Commit 7566081

V K <object.assign@gmail.com>
2024-12-17 17:37:10
feat: direct piping to file shortcut (#1001)
* feat: pipe to file shortcut * docs: update doc to reflect the new pipe signature --------- Co-authored-by: Anton Golub <antongolub@antongolub.com>
1 parent 2a3b19d
Changed files (3)
docs/process-promise.md
@@ -70,6 +70,13 @@ await $`echo "Hello, stdout!"`
 await $`cat /tmp/output.txt`
 ```
 
+You can pass a string to `pipe()` to implicitly create a receiving file. The previous example is equivalent to:
+
+```js
+await $`echo "Hello, stdout!"`
+  .pipe('/tmp/output.txt')
+```
+
 Pipes can be used to show a real-time output of the process:
 
 ```js
src/core.ts
@@ -335,7 +335,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
   pipe<D extends Writable>(dest: D): D & PromiseLike<ProcessOutput & D>
   pipe<D extends ProcessPromise>(dest: D): D
   pipe(
-    dest: Writable | ProcessPromise | TemplateStringsArray,
+    dest: Writable | ProcessPromise | TemplateStringsArray | string,
     ...args: any[]
   ): (Writable & PromiseLike<ProcessPromise & Writable>) | ProcessPromise {
     if (isStringLiteral(dest, ...args))
@@ -347,9 +347,6 @@ export class ProcessPromise extends Promise<ProcessOutput> {
         })(dest as TemplateStringsArray, ...args)
       )
 
-    if (isString(dest))
-      throw new Error('The pipe() method does not take strings. Forgot $?')
-
     this._piped = true
     const ee = this._ee
     const from = new VoidStream()
@@ -371,6 +368,8 @@ export class ProcessPromise extends Promise<ProcessOutput> {
       })
     }
 
+    if (isString(dest)) dest = fs.createWriteStream(dest)
+
     if (dest instanceof ProcessPromise) {
       dest._pipedFrom = this
 
@@ -382,6 +381,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
       }
       return dest
     }
+
     from.once('end', () => dest.emit('end-piped-from')).pipe(dest)
     return promisifyStream(dest, this) as Writable &
       PromiseLike<ProcessPromise & Writable>
test/core.test.js
@@ -421,6 +421,20 @@ describe('core', () => {
         }
       })
 
+      test('accepts file', async () => {
+        const file = tempfile()
+        try {
+          await $`echo foo`.pipe(file)
+          assert.equal((await fs.readFile(file)).toString(), 'foo\n')
+
+          const r = $`cat`
+          fs.createReadStream(file).pipe(r.stdin)
+          assert.equal((await r).stdout, 'foo\n')
+        } finally {
+          await fs.rm(file)
+        }
+      })
+
       test('accepts ProcessPromise', async () => {
         const p = await $`echo foo`.pipe($`cat`)
         assert.equal(p.stdout.trim(), 'foo')
@@ -437,19 +451,6 @@ describe('core', () => {
         assert.equal((await p1).stdout.trim(), 'pipe-to-stdout')
       })
 
-      test('checks argument type', async () => {
-        let err
-        try {
-          $`echo 'test'`.pipe('str')
-        } catch (p) {
-          err = p
-        }
-        assert.equal(
-          err.message,
-          'The pipe() method does not take strings. Forgot $?'
-        )
-      })
-
       describe('supports chaining', () => {
         const getUpperCaseTransform = () =>
           new Transform({