Commit 01af3d5

Anton Golub <antongolub@antongolub.com>
2024-05-21 09:46:38
feat: expose signal prop from ProcessPromise (#816)
* feat: provide format methods for `ProcessPromise` continues #811 relates #764 * feat: expose abortion signal from `ProcessPromise`
1 parent 1f7ce4e
Changed files (2)
src/core.ts
@@ -238,7 +238,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
     this._from = from
     this._resolve = resolve
     this._reject = reject
-    this._snapshot = { ...options }
+    this._snapshot = { ac: new AbortController(), ...options }
   }
 
   run(): ProcessPromise {
@@ -448,12 +448,19 @@ export class ProcessPromise extends Promise<ProcessOutput> {
   }
 
   abort(reason?: string) {
+    if (this.signal !== this._snapshot.ac?.signal)
+      throw new Error('The signal is controlled by another process.')
+
     if (!this.child)
       throw new Error('Trying to abort a process without creating one.')
 
     this._zurk?.ac.abort(reason)
   }
 
+  get signal() {
+    return this._snapshot.signal || this._snapshot.ac?.signal
+  }
+
   async kill(signal = 'SIGTERM'): Promise<void> {
     if (!this.child)
       throw new Error('Trying to kill a process without creating one.')
test/core.test.js
@@ -358,6 +358,45 @@ describe('core', () => {
           assert.match(message, /The operation was aborted/)
         }
       })
+
+      test('exposes `signal` property', async () => {
+        const ac = new AbortController()
+        const p = $({ ac, detached: true })`echo test`
+
+        assert.equal(p.signal, ac.signal)
+        await p
+      })
+
+      test('throws if the signal was previously aborted', async () => {
+        const ac = new AbortController()
+        const { signal } = ac
+        ac.abort('reason')
+
+        try {
+          await $({ signal, detached: true })`sleep 999`
+        } catch ({ message }) {
+          assert.match(message, /The operation was aborted/)
+        }
+      })
+
+      test('throws if the signal is controlled by another process', async () => {
+        const ac = new AbortController()
+        const { signal } = ac
+        const p = $({ signal })`sleep 999`
+
+        try {
+          p.abort()
+        } catch ({ message }) {
+          assert.match(message, /The signal is controlled by another process./)
+        }
+
+        try {
+          ac.abort()
+          await p
+        } catch ({ message }) {
+          assert.match(message, /The operation was aborted/)
+        }
+      })
     })
 
     describe('kill()', () => {