Commit ab7e109
Changed files (20)
build/cli.cjs
@@ -181,7 +181,7 @@ function printUsage() {
--env=<path> path to env file
--experimental enables experimental features (deprecated)
- ${import_index.chalk.italic("Full documentation:")} ${import_index.chalk.underline("https://google.github.io/zx/")}
+ ${import_index.chalk.italic("Full documentation:")} ${import_index.chalk.underline(import_index.Fail.DOCS_URL)}
`);
}
function main() {
@@ -270,7 +270,7 @@ function readScript() {
if (script.length === 0) {
printUsage();
import_node_process2.default.exitCode = 1;
- throw new Error("No script provided");
+ throw new import_index.Fail("No script provided");
}
} else if (/^https?:/.test(firstArg)) {
const { name, ext: ext2 = argv.ext } = import_index.path.parse(new URL(firstArg).pathname);
build/cli.js
build/core.cjs
@@ -17,6 +17,7 @@ const {
var core_exports = {};
__export(core_exports, {
$: () => $,
+ Fail: () => Fail,
ProcessOutput: () => ProcessOutput,
ProcessPromise: () => ProcessPromise,
cd: () => cd,
@@ -203,44 +204,53 @@ var ERRNO_CODES = {
130: "Previous owner died",
131: "State not recoverable"
};
-function getErrnoMessage(errno) {
- return ERRNO_CODES[-errno] || "Unknown error";
-}
-var getExitCodeInfo = (exitCode) => EXIT_CODES[exitCode];
-var formatExitMessage = (code, signal, stderr, from, details = "") => {
- if (code == 0 && signal == null) return `exit code: ${code}`;
- const codeInfo = getExitCodeInfo(code);
- let message = `${stderr}
+var DOCS_URL = "https://google.github.io/zx";
+var _Fail = class _Fail extends Error {
+ static formatExitMessage(code, signal, stderr, from, details = "") {
+ if (code == 0 && signal == null) return `exit code: ${code}`;
+ const codeInfo = _Fail.getExitCodeInfo(code);
+ let message = `${stderr}
at ${from}
exit code: ${code}${codeInfo ? " (" + codeInfo + ")" : ""}`;
- if (signal != null) message += `
+ if (signal != null) message += `
signal: ${signal}`;
- if (details) message += `
+ if (details) message += `
details:
${details}`;
- return message;
-};
-var formatErrorMessage = (err, from) => {
- return `${err.message}
- errno: ${err.errno} (${getErrnoMessage(err.errno)})
+ return message;
+ }
+ static formatErrorMessage(err, from) {
+ return `${err.message}
+ errno: ${err.errno} (${_Fail.getErrnoMessage(err.errno)})
code: ${err.code}
at ${from}`;
+ }
+ static formatErrorDetails(lines = [], lim = 20) {
+ if (lines.length < lim) return lines.join("\n");
+ let errors = lines.filter((l) => /(fail|error|not ok|exception)/i.test(l));
+ if (errors.length === 0) errors = lines;
+ return errors.slice(0, lim).join("\n") + (errors.length > lim ? "\n..." : "");
+ }
+ static getExitCodeInfo(exitCode) {
+ return EXIT_CODES[exitCode];
+ }
+ static getCallerLocationFromString(stackString = "unknown") {
+ const lines = stackString.split(/^\s*(at\s)?/m).filter((s) => s == null ? void 0 : s.includes(":"));
+ const i = lines.findIndex((l) => l.includes("Proxy.set"));
+ const offset = i < 0 ? i : i + 2;
+ return (lines.find((l) => l.includes("file://")) || lines[offset] || stackString).trim();
+ }
+ static getCallerLocation(err = new Error("zx error")) {
+ return _Fail.getCallerLocationFromString(err.stack);
+ }
+ static getErrnoMessage(errno) {
+ return ERRNO_CODES[-errno] || "Unknown error";
+ }
};
-function getCallerLocation(err = new Error("zx error")) {
- return getCallerLocationFromString(err.stack);
-}
-function getCallerLocationFromString(stackString = "unknown") {
- const lines = stackString.split(/^\s*(at\s)?/m).filter((s) => s == null ? void 0 : s.includes(":"));
- const i = lines.findIndex((l) => l.includes("Proxy.set"));
- const offset = i < 0 ? i : i + 2;
- return (lines.find((l) => l.includes("file://")) || lines[offset] || stackString).trim();
-}
-var formatErrorDetails = (lines = [], lim = 20) => {
- if (lines.length < lim) return lines.join("\n");
- let errors = lines.filter((l) => /(fail|error|not ok|exception)/i.test(l));
- if (errors.length === 0) errors = lines;
- return errors.slice(0, lim).join("\n") + (errors.length > lim ? "\n..." : "");
-};
+_Fail.DOCS_URL = DOCS_URL;
+_Fail.EXIT_CODES = EXIT_CODES;
+_Fail.ERRNO_CODES = ERRNO_CODES;
+var Fail = _Fail;
// src/log.ts
var import_vendor_core = require("./vendor-core.cjs");
@@ -427,8 +437,8 @@ var storage = new import_node_async_hooks.AsyncLocalStorage();
var snapshots = [];
var delimiters = [];
var getStore = () => storage.getStore() || defaults;
-var getSnapshot = (snapshot, from, cmd) => __spreadProps(__spreadValues({}, snapshot), {
- ac: snapshot.ac || new AbortController(),
+var getSnapshot = (opts, from, cmd) => __spreadProps(__spreadValues({}, opts), {
+ ac: opts.ac || new AbortController(),
ee: new import_node_events.EventEmitter(),
from,
cmd
@@ -438,17 +448,15 @@ function within(callback) {
}
var $ = new Proxy(
function(pieces, ...args) {
- const snapshot = getStore();
+ const opts = getStore();
if (!Array.isArray(pieces)) {
return function(...args2) {
- return within(
- () => Object.assign($, snapshot, pieces).apply(this, args2)
- );
+ return within(() => Object.assign($, opts, pieces).apply(this, args2));
};
}
- const from = getCallerLocation();
+ const from = Fail.getCallerLocation();
if (pieces.some((p) => p == null))
- throw new Error(`Malformed command at ${from}`);
+ throw new Fail(`Malformed command at ${from}`);
checkShell();
checkQuote();
const cmd = (0, import_vendor_core2.buildCmd)(
@@ -456,7 +464,7 @@ var $ = new Proxy(
pieces,
args
);
- snapshots.push(getSnapshot(snapshot, from, cmd));
+ snapshots.push(getSnapshot(opts, from, cmd));
const pp = new ProcessPromise(import_util.noop);
if (!pp.isHalted()) pp.run();
return pp.sync ? pp.output : pp;
@@ -546,7 +554,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
},
on: {
start: () => {
- $2.log({ kind: "cmd", cmd: self.cmd, cwd, verbose: self.isVerbose(), id });
+ $2.log({ kind: "cmd", cmd: $2.cmd, cwd, verbose: self.isVerbose(), id });
self.timeout($2.timeout, $2.timeoutSignal);
},
stdout: (data) => {
@@ -625,19 +633,19 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
return promisifyStream(dest, this);
}
abort(reason) {
- if (this.isSettled()) throw new Error("Too late to abort the process.");
+ if (this.isSettled()) throw new Fail("Too late to abort the process.");
if (this.signal !== this.ac.signal)
- throw new Error("The signal is controlled by another process.");
+ throw new Fail("The signal is controlled by another process.");
if (!this.child)
- throw new Error("Trying to abort a process without creating one.");
+ throw new Fail("Trying to abort a process without creating one.");
this.ac.abort(reason);
}
kill(signal = $.killSignal) {
- if (this.isSettled()) throw new Error("Too late to kill the process.");
+ if (this.isSettled()) throw new Fail("Too late to kill the process.");
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.");
- return $.kill(this.child.pid, signal);
+ throw new Fail("Trying to kill a process without creating one.");
+ if (!this.pid) throw new Fail("The process pid is undefined.");
+ return $.kill(this.pid, signal);
}
/**
* @deprecated Use $({halt: true})`cmd` instead.
@@ -833,7 +841,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
return;
}
Object.defineProperty(p, k, { configurable: true, get() {
- throw new Error("Inappropriate usage. Apply $ instead of direct instantiation.");
+ throw new Fail("Inappropriate usage. Apply $ instead of direct instantiation.");
} });
});
}
@@ -896,7 +904,7 @@ var _ProcessOutput = class _ProcessOutput extends Error {
}
blob(type = "text/plain") {
if (!globalThis.Blob)
- throw new Error(
+ throw new Fail(
"Blob is not supported in this environment. Provide a polyfill"
);
return new Blob([this.buffer()], { type });
@@ -936,10 +944,10 @@ var _ProcessOutput = class _ProcessOutput extends Error {
}`;
}
};
-_ProcessOutput.getExitMessage = formatExitMessage;
-_ProcessOutput.getErrorMessage = formatErrorMessage;
-_ProcessOutput.getErrorDetails = formatErrorDetails;
-_ProcessOutput.getExitCodeInfo = getExitCodeInfo;
+_ProcessOutput.getExitMessage = Fail.formatExitMessage;
+_ProcessOutput.getErrorMessage = Fail.formatErrorMessage;
+_ProcessOutput.getErrorDetails = Fail.formatErrorDetails;
+_ProcessOutput.getExitCodeInfo = Fail.getExitCodeInfo;
var ProcessOutput = _ProcessOutput;
function usePowerShell() {
$.shell = import_vendor_core2.which.sync("powershell.exe");
@@ -968,14 +976,11 @@ try {
} catch (err) {
}
function checkShell() {
- if (!$.shell)
- throw new Error(`No shell is available: https://google.github.io/zx/shell`);
+ if (!$.shell) throw new Fail(`No shell is available: ${Fail.DOCS_URL}/shell`);
}
function checkQuote() {
if (!$.quote)
- throw new Error(
- "No quote function is defined: https://google.github.io/zx/quotes"
- );
+ throw new Fail(`No quote function is defined: ${Fail.DOCS_URL}/quotes`);
}
var cwdSyncHook;
function syncProcessCwd(flag = true) {
@@ -1052,6 +1057,7 @@ function resolveDefaults(defs = defaults, prefix = ENV_PREFIX, env = import_node
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
$,
+ Fail,
ProcessOutput,
ProcessPromise,
cd,
build/core.d.ts
@@ -6,11 +6,13 @@ import cp, { type ChildProcess, type IOType, type StdioOptions } from 'node:chil
import { type Encoding } from 'node:crypto';
import { type Readable, type Writable } from 'node:stream';
import { inspect } from 'node:util';
+import { Fail } from './error.js';
import { log } from './log.js';
import { type TSpawnStore } from './vendor-core.js';
import { type Duration, quote } from './util.js';
export { default as path } from 'node:path';
export * as os from 'node:os';
+export { Fail } from './error.js';
export { log, type LogEntry } from './log.js';
export { chalk, which, ps } from './vendor-core.js';
export { type Duration, quote, quotePowerShell } from './util.js';
@@ -170,10 +172,10 @@ export declare class ProcessOutput extends Error {
[Symbol.toPrimitive](): string;
[Symbol.iterator](): Iterator<string>;
[inspect.custom](): string;
- static getExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string, details?: string) => string;
- static getErrorMessage: (err: NodeJS.ErrnoException, from: string) => string;
- static getErrorDetails: (lines?: string[], lim?: number) => string;
- static getExitCodeInfo: (exitCode: number | null) => string | undefined;
+ static getExitMessage: typeof Fail.formatExitMessage;
+ static getErrorMessage: typeof Fail.formatErrorMessage;
+ static getErrorDetails: typeof Fail.formatErrorDetails;
+ static getExitCodeInfo: typeof Fail.getExitCodeInfo;
}
export declare function usePowerShell(): void;
export declare function usePwsh(): void;
build/core.js
@@ -3,6 +3,7 @@ import "./deno.js"
import * as __module__ from "./core.cjs"
const {
$,
+ Fail,
ProcessOutput,
ProcessPromise,
cd,
@@ -25,6 +26,7 @@ const {
} = globalThis.Deno ? globalThis.require("./core.cjs") : __module__
export {
$,
+ Fail,
ProcessOutput,
ProcessPromise,
cd,
build/deps.cjs
@@ -24,7 +24,7 @@ function installDeps(dependencies, prefix, registry, installerType = "npm") {
);
if (packages.length === 0) return;
if (!installer) {
- throw new Error(
+ throw new import_index.Fail(
`Unsupported installer type: ${installerType}. Supported types: ${Object.keys(installers).join(", ")}`
);
}
build/error.d.ts
@@ -0,0 +1,12 @@
+export declare class Fail extends Error {
+ static DOCS_URL: string;
+ static EXIT_CODES: Record<number, string>;
+ static ERRNO_CODES: Record<number, string>;
+ static formatExitMessage(code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string, details?: string): string;
+ static formatErrorMessage(err: NodeJS.ErrnoException, from: string): string;
+ static formatErrorDetails(lines?: string[], lim?: number): string;
+ static getExitCodeInfo(exitCode: number | null): string | undefined;
+ static getCallerLocationFromString(stackString?: string): string;
+ static getCallerLocation(err?: Error): string;
+ static getErrnoMessage(errno?: number): string;
+}
build/index.cjs
@@ -159,9 +159,8 @@ function question(_0) {
function stdin() {
return __async(this, arguments, function* (stream = import_node_process.default.stdin) {
let buf = "";
- stream.setEncoding("utf8");
try {
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
+ for (var iter = __forAwait(stream.setEncoding("utf8")), more, temp, error; more = !(temp = yield iter.next()).done; more = false) {
const chunk = temp.value;
buf += chunk;
}
@@ -181,7 +180,7 @@ function stdin() {
function retry(count, d, cb) {
return __async(this, null, function* () {
if (typeof d === "function") return retry(count, 0, d);
- if (!cb) throw new Error("Callback is required for retry");
+ if (!cb) throw new import_core.Fail("Callback is required for retry");
const total = count;
const gen = typeof d === "object" ? d : function* (d2) {
while (true) yield d2;
build/index.js
@@ -28,6 +28,7 @@ const {
updateArgv,
version,
$,
+ Fail,
ProcessOutput,
ProcessPromise,
cd,
@@ -75,6 +76,7 @@ export {
updateArgv,
version,
$,
+ Fail,
ProcessOutput,
ProcessPromise,
cd,
src/cli.ts
@@ -29,6 +29,7 @@ import {
path,
stdin,
VERSION,
+ Fail,
} from './index.ts'
import { installDeps, parseDeps } from './deps.ts'
import { startRepl } from './repl.ts'
@@ -90,7 +91,7 @@ export function printUsage() {
--env=<path> path to env file
--experimental enables experimental features (deprecated)
- ${chalk.italic('Full documentation:')} ${chalk.underline('https://google.github.io/zx/')}
+ ${chalk.italic('Full documentation:')} ${chalk.underline(Fail.DOCS_URL)}
`)
}
@@ -192,7 +193,7 @@ async function readScript() {
if (script.length === 0) {
printUsage()
process.exitCode = 1
- throw new Error('No script provided')
+ throw new Fail('No script provided')
}
} else if (/^https?:/.test(firstArg)) {
const { name, ext = argv.ext } = path.parse(new URL(firstArg).pathname)
src/core.ts
@@ -27,13 +27,7 @@ import process from 'node:process'
import { type Readable, type Writable } from 'node:stream'
import { inspect } from 'node:util'
-import {
- formatErrorDetails,
- formatErrorMessage,
- formatExitMessage,
- getCallerLocation,
- getExitCodeInfo,
-} from './error.ts'
+import { Fail } from './error.ts'
import { log } from './log.ts'
import {
exec,
@@ -66,6 +60,7 @@ import {
export { default as path } from 'node:path'
export * as os from 'node:os'
+export { Fail } from './error.ts'
export { log, type LogEntry } from './log.ts'
export { chalk, which, ps } from './vendor-core.ts'
export { type Duration, quote, quotePowerShell } from './util.ts'
@@ -172,13 +167,9 @@ const delimiters: Options['delimiter'][] = []
const getStore = () => storage.getStore() || defaults
-const getSnapshot = (
- snapshot: Options,
- from: string,
- cmd: string
-): Snapshot => ({
- ...snapshot,
- ac: snapshot.ac || new AbortController(),
+const getSnapshot = (opts: Options, from: string, cmd: string): Snapshot => ({
+ ...opts,
+ ac: opts.ac || new AbortController(),
ee: new EventEmitter(),
from,
cmd,
@@ -193,17 +184,15 @@ export type $ = Shell & Options
export const $: $ = new Proxy<$>(
function (pieces: TemplateStringsArray | Partial<Options>, ...args: any[]) {
- const snapshot = getStore()
+ const opts = getStore()
if (!Array.isArray(pieces)) {
return function (this: any, ...args: any) {
- return within(() =>
- Object.assign($, snapshot, pieces).apply(this, args)
- )
+ return within(() => Object.assign($, opts, pieces).apply(this, args))
}
}
- const from = getCallerLocation()
+ const from = Fail.getCallerLocation()
if (pieces.some((p) => p == null))
- throw new Error(`Malformed command at ${from}`)
+ throw new Fail(`Malformed command at ${from}`)
checkShell()
checkQuote()
@@ -213,7 +202,7 @@ export const $: $ = new Proxy<$>(
pieces as TemplateStringsArray,
args
) as string
- snapshots.push(getSnapshot(snapshot, from, cmd))
+ snapshots.push(getSnapshot(opts, from, cmd))
const pp = new ProcessPromise(noop)
if (!pp.isHalted()) pp.run()
@@ -327,7 +316,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
},
on: {
start: () => {
- $.log({ kind: 'cmd', cmd: self.cmd, cwd, verbose: self.isVerbose(), id })
+ $.log({ kind: 'cmd', cmd: $.cmd, cwd, verbose: self.isVerbose(), id })
self.timeout($.timeout, $.timeoutSignal)
},
stdout: (data) => {
@@ -441,22 +430,22 @@ export class ProcessPromise extends Promise<ProcessOutput> {
}
abort(reason?: string) {
- if (this.isSettled()) throw new Error('Too late to abort the process.')
+ if (this.isSettled()) throw new Fail('Too late to abort the process.')
if (this.signal !== this.ac.signal)
- throw new Error('The signal is controlled by another process.')
+ throw new Fail('The signal is controlled by another process.')
if (!this.child)
- throw new Error('Trying to abort a process without creating one.')
+ throw new Fail('Trying to abort a process without creating one.')
this.ac.abort(reason)
}
kill(signal = $.killSignal): Promise<void> {
- if (this.isSettled()) throw new Error('Too late to kill the process.')
+ if (this.isSettled()) throw new Fail('Too late to kill the process.')
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.')
+ throw new Fail('Trying to kill a process without creating one.')
+ if (!this.pid) throw new Fail('The process pid is undefined.')
- return $.kill(this.child.pid, signal)
+ return $.kill(this.pid, signal)
}
/**
@@ -696,7 +685,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
if (k in Promise.prototype) return
if (!toggle) { Reflect.deleteProperty(p, k); return }
Object.defineProperty(p, k, { configurable: true, get() {
- throw new Error('Inappropriate usage. Apply $ instead of direct instantiation.')
+ throw new Fail('Inappropriate usage. Apply $ instead of direct instantiation.')
}})
})
}
@@ -798,7 +787,7 @@ export class ProcessOutput extends Error {
blob(type = 'text/plain'): Blob {
if (!globalThis.Blob)
- throw new Error(
+ throw new Fail(
'Blob is not supported in this environment. Provide a polyfill'
)
return new Blob([this.buffer()], { type })
@@ -853,13 +842,13 @@ export class ProcessOutput extends Error {
}`
}
- static getExitMessage = formatExitMessage
+ static getExitMessage = Fail.formatExitMessage
- static getErrorMessage = formatErrorMessage
+ static getErrorMessage = Fail.formatErrorMessage
- static getErrorDetails = formatErrorDetails
+ static getErrorDetails = Fail.formatErrorDetails
- static getExitCodeInfo = getExitCodeInfo
+ static getExitCodeInfo = Fail.getExitCodeInfo
}
export function usePowerShell() {
@@ -892,15 +881,12 @@ try {
} catch (err) {}
function checkShell() {
- if (!$.shell)
- throw new Error(`No shell is available: https://google.github.io/zx/shell`)
+ if (!$.shell) throw new Fail(`No shell is available: ${Fail.DOCS_URL}/shell`)
}
function checkQuote() {
if (!$.quote)
- throw new Error(
- 'No quote function is defined: https://google.github.io/zx/quotes'
- )
+ throw new Fail(`No quote function is defined: ${Fail.DOCS_URL}/quotes`)
}
let cwdSyncHook: AsyncHook
src/deps.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import { builtinModules } from 'node:module'
-import { $, spinner } from './index.ts'
+import { $, spinner, Fail } from './index.ts'
import { depseek } from './vendor.ts'
/**
@@ -35,7 +35,7 @@ export async function installDeps(
)
if (packages.length === 0) return
if (!installer) {
- throw new Error(
+ throw new Fail(
`Unsupported installer type: ${installerType}. Supported types: ${Object.keys(installers).join(', ')}`
)
}
src/error.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-const EXIT_CODES = {
+const EXIT_CODES: Record<number, string> = {
2: 'Misuse of shell builtins',
126: 'Invoked command cannot execute',
127: 'Command not found',
@@ -47,7 +47,7 @@ const EXIT_CODES = {
159: 'Bad syscall',
}
-const ERRNO_CODES = {
+const ERRNO_CODES: Record<number, string> = {
0: 'Success',
1: 'Not super-user',
2: 'No such file or directory',
@@ -169,69 +169,77 @@ const ERRNO_CODES = {
131: 'State not recoverable',
}
-export function getErrnoMessage(errno?: number): string {
- return (
- ERRNO_CODES[-(errno as number) as keyof typeof ERRNO_CODES] ||
- 'Unknown error'
- )
-}
+const DOCS_URL = 'https://google.github.io/zx'
-export const getExitCodeInfo = (exitCode: number | null): string | undefined =>
- EXIT_CODES[exitCode as keyof typeof EXIT_CODES]
+export class Fail extends Error {
+ static DOCS_URL = DOCS_URL
+ static EXIT_CODES = EXIT_CODES
+ static ERRNO_CODES = ERRNO_CODES
-export const formatExitMessage = (
- code: number | null,
- signal: NodeJS.Signals | null,
- stderr: string,
- from: string,
- details: string = ''
-): string => {
- if (code == 0 && signal == null) return `exit code: ${code}`
+ static formatExitMessage(
+ code: number | null,
+ signal: NodeJS.Signals | null,
+ stderr: string,
+ from: string,
+ details: string = ''
+ ): string {
+ if (code == 0 && signal == null) return `exit code: ${code}`
- const codeInfo = getExitCodeInfo(code)
- let message = `${stderr}
+ const codeInfo = Fail.getExitCodeInfo(code)
+ let message = `${stderr}
at ${from}
exit code: ${code}${codeInfo ? ' (' + codeInfo + ')' : ''}`
- if (signal != null) message += `\n signal: ${signal}`
+ if (signal != null) message += `\n signal: ${signal}`
- if (details) message += `\n details: \n${details}`
+ if (details) message += `\n details: \n${details}`
- return message
-}
+ return message
+ }
-export const formatErrorMessage = (
- err: NodeJS.ErrnoException,
- from: string
-): string => {
- return `${err.message}
- errno: ${err.errno} (${getErrnoMessage(err.errno)})
+ static formatErrorMessage(err: NodeJS.ErrnoException, from: string): string {
+ return `${err.message}
+ errno: ${err.errno} (${Fail.getErrnoMessage(err.errno)})
code: ${err.code}
at ${from}`
-}
+ }
-export function getCallerLocation(err = new Error('zx error')): string {
- return getCallerLocationFromString(err.stack)
-}
+ static formatErrorDetails(lines: string[] = [], lim = 20): string {
+ if (lines.length < lim) return lines.join('\n')
-export function getCallerLocationFromString(stackString = 'unknown'): string {
- const lines = stackString
- .split(/^\s*(at\s)?/m)
- .filter((s) => s?.includes(':'))
- const i = lines.findIndex((l) => l.includes('Proxy.set'))
- const offset = i < 0 ? i : i + 2
-
- return (
- lines.find((l) => l.includes('file://')) ||
- lines[offset] ||
- stackString
- ).trim()
-}
+ let errors = lines.filter((l) => /(fail|error|not ok|exception)/i.test(l))
+ if (errors.length === 0) errors = lines
+ return (
+ errors.slice(0, lim).join('\n') + (errors.length > lim ? '\n...' : '')
+ )
+ }
+
+ static getExitCodeInfo(exitCode: number | null): string | undefined {
+ return EXIT_CODES[exitCode as keyof typeof EXIT_CODES]
+ }
+
+ static getCallerLocationFromString(stackString = 'unknown'): string {
+ const lines = stackString
+ .split(/^\s*(at\s)?/m)
+ .filter((s) => s?.includes(':'))
+ const i = lines.findIndex((l) => l.includes('Proxy.set'))
+ const offset = i < 0 ? i : i + 2
+
+ return (
+ lines.find((l) => l.includes('file://')) ||
+ lines[offset] ||
+ stackString
+ ).trim()
+ }
-export const formatErrorDetails = (lines: string[] = [], lim = 20): string => {
- if (lines.length < lim) return lines.join('\n')
+ static getCallerLocation(err: Error = new Error('zx error')): string {
+ return Fail.getCallerLocationFromString(err.stack)
+ }
- let errors = lines.filter((l) => /(fail|error|not ok|exception)/i.test(l))
- if (errors.length === 0) errors = lines
- return errors.slice(0, lim).join('\n') + (errors.length > lim ? '\n...' : '')
+ static getErrnoMessage(errno?: number): string {
+ return (
+ ERRNO_CODES[-(errno as number) as keyof typeof ERRNO_CODES] ||
+ 'Unknown error'
+ )
+ }
}
src/goods.ts
@@ -24,6 +24,7 @@ import {
type ProcessPromise,
path,
os,
+ Fail,
} from './core.ts'
import {
type Duration,
@@ -153,7 +154,7 @@ export function echo(pieces: TemplateStringsArray, ...args: any[]) {
console.log(msg)
}
-function stringify(arg: ProcessOutput | any) {
+function stringify(arg: any) {
return arg instanceof ProcessOutput ? arg.toString().trimEnd() : `${arg}`
}
@@ -193,8 +194,7 @@ export async function question(
export async function stdin(stream: Readable = process.stdin): Promise<string> {
let buf = ''
- stream.setEncoding('utf8')
- for await (const chunk of stream) {
+ for await (const chunk of stream.setEncoding('utf8')) {
buf += chunk
}
return buf
@@ -212,7 +212,7 @@ export async function retry<T>(
cb?: () => T
): Promise<T> {
if (typeof d === 'function') return retry(count, 0, d)
- if (!cb) throw new Error('Callback is required for retry')
+ if (!cb) throw new Fail('Callback is required for retry')
const total = count
const gen =
test/core.test.js
@@ -32,6 +32,7 @@ import {
usePowerShell,
usePwsh,
useBash,
+ Fail,
} from '../build/core.js'
import {
tempfile,
@@ -215,6 +216,14 @@ describe('core', () => {
test('malformed cmd error', async () => {
assert.throws(() => $`\033`, /malformed/i)
+
+ try {
+ $([null])
+ throw new Err('unreachable')
+ } catch (e) {
+ assert.ok(e instanceof Fail)
+ assert.match(e.message, /malformed/i)
+ }
})
test('snapshots works', async () => {
test/error.test.ts
@@ -14,7 +14,9 @@
import assert from 'node:assert'
import { test, describe } from 'node:test'
-import {
+import { Fail } from '../src/error.ts'
+
+const {
getErrnoMessage,
getExitCodeInfo,
getCallerLocation,
@@ -22,7 +24,7 @@ import {
formatExitMessage,
formatErrorMessage,
formatErrorDetails,
-} from '../src/error.ts'
+} = Fail
describe('error', () => {
test('getExitCodeInfo()', () => {
test/export.test.js
@@ -23,6 +23,10 @@ import * as vendor from '../build/vendor.js'
describe('core', () => {
test('exports', () => {
assert.equal(typeof core.$, 'function', 'core.$')
+ assert.equal(typeof core.Fail, 'function', 'core.Fail')
+ assert.equal(typeof core.Fail.DOCS_URL, 'string', 'core.Fail.DOCS_URL')
+ assert.equal(typeof core.Fail.ERRNO_CODES, 'object', 'core.Fail.ERRNO_CODES')
+ assert.equal(typeof core.Fail.EXIT_CODES, 'object', 'core.Fail.EXIT_CODES')
assert.equal(typeof core.ProcessOutput, 'function', 'core.ProcessOutput')
assert.equal(typeof core.ProcessOutput.getErrorDetails, 'function', 'core.ProcessOutput.getErrorDetails')
assert.equal(typeof core.ProcessOutput.getErrorMessage, 'function', 'core.ProcessOutput.getErrorMessage')
@@ -145,6 +149,10 @@ describe('cli', () => {
describe('index', () => {
test('exports', () => {
assert.equal(typeof index.$, 'function', 'index.$')
+ assert.equal(typeof index.Fail, 'function', 'index.Fail')
+ assert.equal(typeof index.Fail.DOCS_URL, 'string', 'index.Fail.DOCS_URL')
+ assert.equal(typeof index.Fail.ERRNO_CODES, 'object', 'index.Fail.ERRNO_CODES')
+ assert.equal(typeof index.Fail.EXIT_CODES, 'object', 'index.Fail.EXIT_CODES')
assert.equal(typeof index.ProcessOutput, 'function', 'index.ProcessOutput')
assert.equal(typeof index.ProcessOutput.getErrorDetails, 'function', 'index.ProcessOutput.getErrorDetails')
assert.equal(typeof index.ProcessOutput.getErrorMessage, 'function', 'index.ProcessOutput.getErrorMessage')
test/package.test.js
@@ -51,6 +51,7 @@ describe('package', () => {
'build/deno.js',
'build/deps.cjs',
'build/deps.d.ts',
+ 'build/error.d.ts',
'build/esblib.cjs',
'build/globals.cjs',
'build/globals.d.ts',
.size-limit.json
@@ -15,7 +15,7 @@
"README.md",
"LICENSE"
],
- "limit": "121.15 kB",
+ "limit": "121.301 kB",
"brotli": false,
"gzip": false
},
@@ -29,14 +29,14 @@
"build/globals.js",
"build/deno.js"
],
- "limit": "812.10 kB",
+ "limit": "812.40 kB",
"brotli": false,
"gzip": false
},
{
"name": "libdefs",
"path": "build/*.d.ts",
- "limit": "39.00 kB",
+ "limit": "39.65 kB",
"brotli": false,
"gzip": false
},
@@ -62,7 +62,7 @@
"README.md",
"LICENSE"
],
- "limit": "867.95 kB",
+ "limit": "868.90 kB",
"brotli": false,
"gzip": false
}
package.json
@@ -74,7 +74,7 @@
"build:js": "node scripts/build-js.mjs --format=cjs --hybrid --entry=src/*.ts:!src/error.ts:!src/repl.ts:!src/md.ts:!src/log.ts:!src/globals-jsr.ts:!src/goods.ts && npm run build:vendor",
"build:vendor": "node scripts/build-js.mjs --format=cjs --entry=src/vendor-*.ts --bundle=all --external='./internals.ts'",
"build:tests": "node scripts/build-tests.mjs",
- "build:dts": "tsc --project tsconfig.json && rm build/error.d.ts build/repl.d.ts build/globals-jsr.d.ts && node scripts/build-dts.mjs",
+ "build:dts": "tsc --project tsconfig.json && rm build/repl.d.ts build/globals-jsr.d.ts && node scripts/build-dts.mjs",
"build:dcr": "docker build -f ./dcr/Dockerfile . -t zx",
"build:jsr": "node scripts/build-jsr.mjs",
"docs:dev": "vitepress dev docs",