Commit afcb0eb
Changed files (6)
build
docs
src
test
smoke
build/core.cjs
@@ -1010,8 +1010,18 @@ function cd(dir) {
}
function kill(_0) {
return __async(this, arguments, function* (pid, signal = $.killSignal) {
- const children = yield import_vendor_core2.ps.tree({ pid, recursive: true });
- for (const p of children) {
+ if (import_node_process2.default.platform === "win32" && (yield new Promise((resolve) => {
+ (0, import_vendor_core2.exec)({
+ cmd: `taskkill /pid ${pid} /t /f`,
+ on: {
+ end({ error }) {
+ resolve(!error);
+ }
+ }
+ });
+ })))
+ return;
+ for (const p of yield import_vendor_core2.ps.tree({ pid, recursive: true })) {
try {
import_node_process2.default.kill(+p.pid, signal);
} catch (e) {
docs/configuration.md
@@ -12,10 +12,26 @@ Or use a CLI argument: `--shell=/bin/bash`
## `$.spawn`
-Specifies a `spawn` api. Defaults to `require('child_process').spawn`.
+Specifies a `spawn` api. Defaults to native `child_process.spawn`.
To override a sync API implementation, set `$.spawnSync` correspondingly.
+## `$.kill`
+Specifies a `kill` function. The default implements _half-graceful shutdown_ via `ps.tree()`. You can override with more sophisticated logic.
+
+```js
+import treekill from 'tree-kill'
+
+$.kill = (pid, signal = 'SIGTERM') => {
+ return new Promise((resolve, reject) => {
+ treekill(pid, signal, (err) => {
+ if (err) reject(err)
+ else resolve()
+ })
+ })
+}
+```
+
## `$.prefix`
Specifies the command that will be prefixed to all commands run.
@@ -160,6 +176,7 @@ $.defaults = {
spawn: childProcess.spawn,
spawnSync: childProcess.spawnSync,
log: $.log,
+ kill: $.kill,
killSignal: 'SIGTERM',
timeoutSignal: 'SIGTERM',
delimiter: /\r?\n/,
src/core.ts
@@ -64,6 +64,7 @@ import {
bufArrJoin,
} from './util.ts'
import { log } from './log.ts'
+import * as child_process from 'node:child_process'
export { default as path } from 'node:path'
export * as os from 'node:os'
@@ -928,8 +929,15 @@ export function cd(dir: string | ProcessOutput) {
}
export async function kill(pid: number, signal = $.killSignal) {
- const children = await ps.tree({ pid, recursive: true })
- for (const p of children) {
+ if (
+ process.platform === 'win32' &&
+ (await new Promise((resolve) => {
+ child_process.exec(`taskkill /pid ${pid} /t /f`, (err) => resolve(!err))
+ }))
+ )
+ return
+
+ for (const p of await ps.tree({ pid, recursive: true })) {
try {
process.kill(+p.pid, signal)
} catch (e) {}
test/smoke/win32.test.js
@@ -72,6 +72,20 @@ _describe('win32', () => {
assert.equal(root.pid, process.pid)
})
+ test('kill works', async () => {
+ const p = $({ nothrow: true })`sleep 100`
+ const { pid } = p
+ const found = await ps.lookup({ pid })
+ console.log('found:', found)
+ assert.equal(found.length, 1)
+ assert.equal(found[0].pid, pid)
+
+ await p.kill()
+ const killed = await ps.lookup({ pid })
+ console.log('killed:', killed)
+ assert.equal(killed.length, 0)
+ })
+
test('abort controller works', async () => {
const ac = new AbortController()
const { signal } = ac
@@ -94,6 +108,7 @@ _describe('win32', () => {
const o = await p
assert.equal(o.signal, 'SIGTERM')
+ assert.throws(() => p.abort(), /Too late to abort the process/)
assert.throws(() => p.kill(), /Too late to kill the process/)
})
})
test/core.test.js
@@ -1178,15 +1178,16 @@ describe('core', () => {
describe('timeout()', () => {
test('expiration works', async () => {
+ await $`sleep 1`.timeout(1000)
let exitCode, signal
try {
- await $`sleep 1`.timeout(999)
+ await $`sleep 1`.timeout(200)
} catch (p) {
exitCode = p.exitCode
signal = p.signal
}
- assert.equal(exitCode, undefined)
- assert.equal(signal, undefined)
+ assert.equal(exitCode, null)
+ assert.equal(signal, 'SIGTERM')
})
test('accepts a signal opt', async () => {
.size-limit.json
@@ -15,7 +15,7 @@
"README.md",
"LICENSE"
],
- "limit": "122.90 kB",
+ "limit": "123.15 kB",
"brotli": false,
"gzip": false
},
@@ -29,7 +29,7 @@
"build/globals.js",
"build/deno.js"
],
- "limit": "813.40 kB",
+ "limit": "813.70 kB",
"brotli": false,
"gzip": false
},
@@ -62,7 +62,7 @@
"README.md",
"LICENSE"
],
- "limit": "869.50 kB",
+ "limit": "869.70 kB",
"brotli": false,
"gzip": false
}