Commit 12c8c60
Changed files (8)
build/core.cjs
@@ -43,6 +43,8 @@ var import_node_fs = __toESM(require("fs"), 1);
var import_node_util2 = require("util");
var import_node_os = require("os");
var import_node_events = require("events");
+var import_node_buffer = require("buffer");
+var import_node_process2 = __toESM(require("process"), 1);
// src/error.ts
var EXIT_CODES = {
@@ -388,14 +390,13 @@ function formatCmd(cmd) {
// src/core.ts
var import_node_path = __toESM(require("path"), 1);
var os = __toESM(require("os"), 1);
-var import_node_buffer = require("buffer");
-var import_node_process2 = __toESM(require("process"), 1);
var import_vendor_core3 = require("./vendor-core.cjs");
var import_util2 = require("./util.cjs");
var CWD = Symbol("processCwd");
var SYNC = Symbol("syncExec");
var EOL = import_node_buffer.Buffer.from(import_node_os.EOL);
var BR_CC = "\n".charCodeAt(0);
+var DLMTR = /\r?\n/;
var SIGTERM = "SIGTERM";
var ENV_PREFIX = "ZX_";
var ENV_ALLOWED = /* @__PURE__ */ new Set([
@@ -439,7 +440,8 @@ var defaults = resolveDefaults({
killSignal: SIGTERM,
timeoutSignal: SIGTERM
});
-var bound = [];
+var boundCtxs = [];
+var delimiters = [];
var $ = new Proxy(
function(pieces, ...args) {
const snapshot = getStore();
@@ -462,7 +464,7 @@ var $ = new Proxy(
args
);
const sync = snapshot[SYNC];
- bound.push([cmd, from, snapshot]);
+ boundCtxs.push([cmd, from, snapshot]);
const process3 = new ProcessPromise(import_util.noop);
if (!process3.isHalted() || sync) process3.run();
return sync ? process3.output : process3;
@@ -503,8 +505,8 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
this._resolve = import_util.noop;
// Stream-like API
this.writable = true;
- if (bound.length) {
- const [cmd, from, snapshot] = bound.pop();
+ if (boundCtxs.length) {
+ const [cmd, from, snapshot] = boundCtxs.pop();
this._command = cmd;
this._from = from;
this._resolve = resolve;
@@ -745,8 +747,8 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
text(encoding) {
return this.then((p) => p.text(encoding));
}
- lines() {
- return this.then((p) => p.lines());
+ lines(delimiter) {
+ return this.then((p) => p.lines(delimiter));
}
buffer() {
return this.then((p) => p.buffer());
@@ -787,13 +789,14 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
[Symbol.asyncIterator]() {
return __asyncGenerator(this, null, function* () {
const memo = [];
+ const dlmtr = this._snapshot.delimiter || $.delimiter || DLMTR;
for (const chunk of this._zurk.store.stdout) {
- yield* __yieldStar((0, import_util.getLines)(chunk, memo));
+ yield* __yieldStar((0, import_util.getLines)(chunk, memo, dlmtr));
}
try {
for (var iter = __forAwait(this.stdout[Symbol.asyncIterator] ? this.stdout : import_vendor_core2.VoidStream.from(this.stdout)), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
const chunk = temp.value;
- yield* __yieldStar((0, import_util.getLines)(chunk, memo));
+ yield* __yieldStar((0, import_util.getLines)(chunk, memo, dlmtr));
}
} catch (temp) {
error = [temp];
@@ -912,7 +915,8 @@ var _ProcessOutput = class _ProcessOutput extends Error {
text(encoding = "utf8") {
return encoding === "utf8" ? this.toString() : this.buffer().toString(encoding);
}
- lines() {
+ lines(delimiter) {
+ delimiters.push(delimiter);
return [...this];
}
valueOf() {
@@ -920,8 +924,9 @@ var _ProcessOutput = class _ProcessOutput extends Error {
}
*[Symbol.iterator]() {
const memo = [];
+ const dlmtr = delimiters.pop() || this._dto.delimiter || $.delimiter || DLMTR;
for (const chunk of this._dto.store.stdall) {
- yield* __yieldStar((0, import_util.getLines)(chunk, memo));
+ yield* __yieldStar((0, import_util.getLines)(chunk, memo, dlmtr));
}
if (memo[0]) yield memo[0];
}
build/core.d.ts
@@ -5,12 +5,12 @@ import { type ChildProcess, type IOType, type StdioOptions, spawn, spawnSync } f
import { type Encoding } from 'node:crypto';
import { type Readable, type Writable } from 'node:stream';
import { inspect } from 'node:util';
+import { Buffer } from 'node:buffer';
import { type TSpawnStore } from './vendor-core.js';
import { type Duration, quote } from './util.js';
import { log } from './log.js';
export { default as path } from 'node:path';
export * as os from 'node:os';
-import { Buffer } from 'node:buffer';
export { log, type LogEntry } from './log.js';
export { chalk, which, ps } from './vendor-core.js';
export { quote, quotePowerShell } from './util.js';
@@ -45,6 +45,7 @@ export interface Options {
kill: typeof kill;
killSignal?: NodeJS.Signals;
halt?: boolean;
+ delimiter?: string | RegExp;
}
export declare const defaults: Options;
export interface Shell<S = false, R = S extends true ? ProcessOutput : ProcessPromise> {
@@ -121,7 +122,7 @@ export declare class ProcessPromise extends Promise<ProcessOutput> {
timeout(d: Duration, signal?: NodeJS.Signals | undefined): ProcessPromise;
json<T = any>(): Promise<T>;
text(encoding?: Encoding): Promise<string>;
- lines(): Promise<string[]>;
+ lines(delimiter?: string | RegExp): Promise<string[]>;
buffer(): Promise<Buffer>;
blob(type?: string): Promise<Blob>;
isQuiet(): boolean;
@@ -149,6 +150,7 @@ type ProcessDto = {
error: any;
from: string;
store: TSpawnStore;
+ delimiter?: string | RegExp;
};
export declare class ProcessOutput extends Error {
private readonly _dto;
@@ -170,7 +172,7 @@ export declare class ProcessOutput extends Error {
buffer(): Buffer;
blob(type?: string): Blob;
text(encoding?: Encoding): string;
- lines(): string[];
+ lines(delimiter?: string | RegExp): string[];
valueOf(): string;
[Symbol.iterator](): Iterator<string>;
static getExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string, details?: string) => string;
build/util.cjs
@@ -112,8 +112,8 @@ var proxyOverride = (origin, ...fallbacks) => new Proxy(origin, {
});
var toCamelCase = (str) => str.toLowerCase().replace(/([a-z])[_-]+([a-z])/g, (_, p1, p2) => p1 + p2.toUpperCase());
var parseBool = (v) => v === "true" || v !== "false" && v;
-var getLines = (chunk, next) => {
- const lines = ((next.pop() || "") + bufToString(chunk)).split(/\r?\n/);
+var getLines = (chunk, next, delimiter) => {
+ const lines = ((next.pop() || "") + bufToString(chunk)).split(delimiter);
next.push(lines.pop());
return lines;
};
build/util.d.ts
@@ -26,4 +26,4 @@ export declare const once: <T extends (...args: any[]) => any>(fn: T) => (...arg
export declare const proxyOverride: <T extends object>(origin: T, ...fallbacks: any) => T;
export declare const toCamelCase: (str: string) => string;
export declare const parseBool: (v: string) => boolean | string;
-export declare const getLines: (chunk: Buffer | string, next: (string | undefined)[]) => string[];
+export declare const getLines: (chunk: Buffer | string, next: (string | undefined)[], delimiter: string | RegExp) => string[];
src/core.ts
@@ -26,6 +26,8 @@ import fs from 'node:fs'
import { inspect } from 'node:util'
import { EOL as _EOL } from 'node:os'
import { EventEmitter } from 'node:events'
+import { Buffer } from 'node:buffer'
+import process from 'node:process'
import {
findErrors,
formatErrorMessage,
@@ -61,13 +63,10 @@ import {
randomId,
bufArrJoin,
} from './util.ts'
-
import { log } from './log.ts'
export { default as path } from 'node:path'
export * as os from 'node:os'
-import { Buffer } from 'node:buffer'
-import process from 'node:process'
export { log, type LogEntry } from './log.ts'
export { chalk, which, ps } from './vendor-core.ts'
export { quote, quotePowerShell } from './util.ts'
@@ -76,6 +75,7 @@ const CWD = Symbol('processCwd')
const SYNC = Symbol('syncExec')
const EOL = Buffer.from(_EOL)
const BR_CC = '\n'.charCodeAt(0)
+const DLMTR = /\r?\n/
const SIGTERM = 'SIGTERM'
const ENV_PREFIX = 'ZX_'
const ENV_ALLOWED: Set<string> = new Set([
@@ -129,6 +129,7 @@ export interface Options {
kill: typeof kill
killSignal?: NodeJS.Signals
halt?: boolean
+ delimiter?: string | RegExp
}
// prettier-ignore
@@ -166,7 +167,8 @@ export interface Shell<
(opts: Partial<Omit<Options, 'sync'>>): Shell<true>
}
}
-const bound: [string, string, Options][] = []
+const boundCtxs: [string, string, Options][] = []
+const delimiters: Array<string | RegExp | undefined> = []
export const $: Shell & Options = new Proxy<Shell & Options>(
function (pieces: TemplateStringsArray | Partial<Options>, ...args: any) {
@@ -192,7 +194,7 @@ export const $: Shell & Options = new Proxy<Shell & Options>(
args
) as string
const sync = snapshot[SYNC]
- bound.push([cmd, from, snapshot])
+ boundCtxs.push([cmd, from, snapshot])
const process = new ProcessPromise(noop)
if (!process.isHalted() || sync) process.run()
@@ -259,8 +261,8 @@ export class ProcessPromise extends Promise<ProcessOutput> {
executor(...args)
})
- if (bound.length) {
- const [cmd, from, snapshot] = bound.pop()!
+ if (boundCtxs.length) {
+ const [cmd, from, snapshot] = boundCtxs.pop()!
this._command = cmd
this._from = from
this._resolve = resolve!
@@ -571,8 +573,8 @@ export class ProcessPromise extends Promise<ProcessOutput> {
return this.then((p) => p.text(encoding))
}
- lines(): Promise<string[]> {
- return this.then((p) => p.lines())
+ lines(delimiter?: string | RegExp): Promise<string[]> {
+ return this.then((p) => p.lines(delimiter))
}
buffer(): Promise<Buffer> {
@@ -634,15 +636,16 @@ export class ProcessPromise extends Promise<ProcessOutput> {
// Async iterator API
async *[Symbol.asyncIterator](): AsyncIterator<string> {
const memo: (string | undefined)[] = []
+ const dlmtr = this._snapshot.delimiter || $.delimiter || DLMTR
for (const chunk of this._zurk!.store.stdout) {
- yield* getLines(chunk, memo)
+ yield* getLines(chunk, memo, dlmtr)
}
for await (const chunk of this.stdout[Symbol.asyncIterator]
? this.stdout
: VoidStream.from(this.stdout)) {
- yield* getLines(chunk, memo)
+ yield* getLines(chunk, memo, dlmtr)
}
if (memo[0]) yield memo[0]
@@ -697,6 +700,7 @@ type ProcessDto = {
error: any
from: string
store: TSpawnStore
+ delimiter?: string | RegExp
}
export class ProcessOutput extends Error {
@@ -798,7 +802,8 @@ export class ProcessOutput extends Error {
: this.buffer().toString(encoding)
}
- lines(): string[] {
+ lines(delimiter?: string | RegExp): string[] {
+ delimiters.push(delimiter)
return [...this]
}
@@ -808,9 +813,11 @@ export class ProcessOutput extends Error {
*[Symbol.iterator](): Iterator<string> {
const memo: (string | undefined)[] = []
+ const dlmtr =
+ delimiters.pop() || this._dto.delimiter || $.delimiter || DLMTR
for (const chunk of this._dto.store.stdall) {
- yield* getLines(chunk, memo)
+ yield* getLines(chunk, memo, dlmtr)
}
if (memo[0]) yield memo[0]
src/util.ts
@@ -172,9 +172,10 @@ export const parseBool = (v: string): boolean | string =>
export const getLines = (
chunk: Buffer | string,
- next: (string | undefined)[]
+ next: (string | undefined)[],
+ delimiter: string | RegExp
) => {
- const lines = ((next.pop() || '') + bufToString(chunk)).split(/\r?\n/)
+ const lines = ((next.pop() || '') + bufToString(chunk)).split(delimiter)
next.push(lines.pop())
return lines
}
test/core.test.js
@@ -1025,7 +1025,6 @@ describe('core', () => {
it('should process all output before handling a non-zero exit code', async () => {
const process = $`sleep 0.1; echo foo; sleep 0.1; echo bar; sleep 0.1; exit 1;`
-
const chunks = []
let errorCaught = null
@@ -1050,11 +1049,22 @@ describe('core', () => {
})
it('handles .nothrow() correctly', async () => {
- const data = []
+ const lines = []
for await (const line of $({ nothrow: true })`grep any test`) {
- data.push(line)
+ lines.push(line)
+ }
+ assert.equal(lines.length, 0, 'Should not yield any lines')
+ })
+
+ it('handles a custom delimiter', async () => {
+ const lines = []
+ for await (const line of $({
+ delimiter: '\0',
+ cwd: tempdir(),
+ })`touch foo bar baz; find ./ -type f -print0 -maxdepth 1`) {
+ lines.push(line)
}
- assert.equal(data.length, 0, 'Should not yield any lines')
+ assert.deepEqual(lines.sort(), ['./bar', './baz', './foo'])
})
})
@@ -1153,6 +1163,15 @@ describe('core', () => {
const p2 = $.sync`echo 'foo\nbar\r\nbaz'`
assert.deepEqual(p2.lines(), ['foo', 'bar', 'baz'])
+
+ const p3 = $({
+ cwd: await tempdir(),
+ })`touch foo bar baz; find ./ -type f -print0 -maxdepth 1`
+ assert.deepEqual((await p3.lines('\0')).sort(), [
+ './bar',
+ './baz',
+ './foo',
+ ])
})
test('buffer()', async () => {
@@ -1230,8 +1249,12 @@ describe('core', () => {
})
test('lines()', async () => {
- const o = new ProcessOutput(null, null, '', '', 'foo\nbar\r\nbaz\n')
- assert.deepEqual(o.lines(), ['foo', 'bar', 'baz'])
+ const o1 = new ProcessOutput(null, null, '', '', 'foo\nbar\r\nbaz\n')
+ assert.deepEqual(o1.lines(), ['foo', 'bar', 'baz'])
+
+ const o2 = new ProcessOutput(null, null, '', '', 'foo\0bar\0baz\0')
+ assert.deepEqual(o2.lines(), ['foo\0bar\0baz\0'])
+ assert.deepEqual(o2.lines('\0'), ['foo', 'bar', 'baz'])
})
test('buffer()', async () => {
.size-limit.json
@@ -17,35 +17,35 @@
"README.md",
"LICENSE"
],
- "limit": "121.8 kB",
+ "limit": "122.25 kB",
"brotli": false,
"gzip": false
},
{
"name": "js parts",
"path": "build/*.{js,cjs}",
- "limit": "816.20 kB",
+ "limit": "816.50 kB",
"brotli": false,
"gzip": false
},
{
"name": "libdefs",
"path": "build/*.d.ts",
- "limit": "40.2 kB",
+ "limit": "40.26 kB",
"brotli": false,
"gzip": false
},
{
"name": "vendor",
"path": "build/vendor-*",
- "limit": "769.1 kB",
+ "limit": "769.10 kB",
"brotli": false,
"gzip": false
},
{
"name": "all",
"path": ["build/*", "man/*", "README.md", "LICENSE"],
- "limit": "872.65 kB",
+ "limit": "873.10 kB",
"brotli": false,
"gzip": false
}