Skip to content

Commit 1555d01

Browse files
committed
feat: provide sync API
closes google#681
1 parent 0640b80 commit 1555d01

File tree

2 files changed

+57
-11
lines changed

2 files changed

+57
-11
lines changed

src/core.ts

+50-11
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,23 @@ import {
4141
export interface Shell {
4242
(pieces: TemplateStringsArray, ...args: any[]): ProcessPromise
4343
(opts: Partial<Options>): Shell
44+
sync: {
45+
(pieces: TemplateStringsArray, ...args: any[]): ProcessOutput
46+
(opts: Partial<Options>): Shell
47+
}
4448
}
4549

4650
const processCwd = Symbol('processCwd')
51+
const syncExec = Symbol('syncExec')
4752

4853
export interface Options {
4954
[processCwd]: string
55+
[syncExec]: boolean
5056
cwd?: string
5157
ac?: AbortController
5258
input?: string | Buffer | Readable | ProcessOutput | ProcessPromise
5359
verbose: boolean
60+
sync: boolean
5461
env: NodeJS.ProcessEnv
5562
shell: string | boolean
5663
nothrow: boolean
@@ -73,8 +80,10 @@ hook.enable()
7380

7481
export const defaults: Options = {
7582
[processCwd]: process.cwd(),
83+
[syncExec]: false,
7684
verbose: true,
7785
env: process.env,
86+
sync: false,
7887
shell: true,
7988
nothrow: false,
8089
quiet: false,
@@ -127,18 +136,35 @@ export const $: Shell & Options = new Proxy<Shell & Options>(
127136
args
128137
) as string
129138

130-
promise._bind(cmd, from, resolve!, reject!, getStore())
139+
const snapshot = getStore()
140+
const sync = snapshot[syncExec]
141+
const callback = () => promise.isHalted || promise.run()
142+
143+
promise._bind(
144+
cmd,
145+
from,
146+
resolve!,
147+
(v: ProcessOutput) => {
148+
reject!(v)
149+
if (sync) throw v
150+
},
151+
snapshot
152+
)
131153
// Postpone run to allow promise configuration.
132-
setImmediate(() => promise.isHalted || promise.run())
133-
return promise
154+
sync ? callback() : setImmediate(callback)
155+
156+
return sync ? promise.output : promise
134157
} as Shell & Options,
135158
{
136159
set(_, key, value) {
137160
const target = key in Function.prototype ? _ : getStore()
138-
Reflect.set(target, key, value)
161+
Reflect.set(target, key === 'sync' ? syncExec : key, value)
162+
139163
return true
140164
},
141165
get(_, key) {
166+
if (key === 'sync') return $({ sync: true })
167+
142168
const target = key in Function.prototype ? _ : getStore()
143169
return Reflect.get(target, key)
144170
},
@@ -169,7 +195,8 @@ export class ProcessPromise extends Promise<ProcessOutput> {
169195
private _resolved = false
170196
private _halted = false
171197
private _piped = false
172-
private zurk: ReturnType<typeof exec> | null = null
198+
private _zurk: ReturnType<typeof exec> | null = null
199+
private _output: ProcessOutput | null = null
173200
_prerun = noop
174201
_postrun = noop
175202

@@ -203,7 +230,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
203230
verbose: self.isVerbose(),
204231
})
205232

206-
this.zurk = exec({
233+
this._zurk = exec({
207234
input,
208235
cmd: $.prefix + this._command,
209236
cwd: $.cwd ?? $[processCwd],
@@ -212,7 +239,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
212239
env: $.env,
213240
spawn: $.spawn,
214241
stdio: this._stdio as any,
215-
sync: false,
242+
sync: $[syncExec],
216243
detached: !isWin,
217244
run: (cb) => cb(),
218245
on: {
@@ -241,9 +268,16 @@ export class ProcessPromise extends Promise<ProcessOutput> {
241268
const message = ProcessOutput.getErrorMessage(error, self._from)
242269
// Should we enable this?
243270
// (nothrow ? self._resolve : self._reject)(
244-
self._reject(
245-
new ProcessOutput(null, null, stdout, stderr, stdall, message)
271+
const output = new ProcessOutput(
272+
null,
273+
null,
274+
stdout,
275+
stderr,
276+
stdall,
277+
message
246278
)
279+
self._output = output
280+
self._reject(output)
247281
} else {
248282
const message = ProcessOutput.getExitMessage(
249283
status,
@@ -259,6 +293,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
259293
stdall,
260294
message
261295
)
296+
self._output = output
262297
if (status === 0 || (self._nothrow ?? $.nothrow)) {
263298
self._resolve(output)
264299
} else {
@@ -275,7 +310,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
275310
}
276311

277312
get child() {
278-
return this.zurk?.child
313+
return this._zurk?.child
279314
}
280315

281316
get stdin(): Writable {
@@ -366,7 +401,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
366401
if (!this.child)
367402
throw new Error('Trying to abort a process without creating one.')
368403

369-
this.zurk?.ac.abort(reason)
404+
this._zurk?.ac.abort(reason)
370405
}
371406

372407
async kill(signal = 'SIGTERM'): Promise<void> {
@@ -419,6 +454,10 @@ export class ProcessPromise extends Promise<ProcessOutput> {
419454
get isHalted(): boolean {
420455
return this._halted
421456
}
457+
458+
get output() {
459+
return this._output
460+
}
422461
}
423462

424463
export class ProcessOutput extends Error {

test/core.test.js

+7
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ describe('core', () => {
120120
assert.equal((await p5).stdout, 'baz')
121121
})
122122

123+
test('`$.sync()` provides synchronous API', () => {
124+
const o1 = $.sync`echo foo`
125+
const o2 = $({ sync: true })`echo foo`
126+
assert.equal(o1.stdout, 'foo\n')
127+
assert.equal(o2.stdout, 'foo\n')
128+
})
129+
123130
test('pipes are working', async () => {
124131
let { stdout } = await $`echo "hello"`
125132
.pipe($`awk '{print $1" world"}'`)

0 commit comments

Comments
 (0)