@kinlab/kin-mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/bin/kin-mcp.js +6 -0
- package/package.json +41 -0
- package/src/index.js +385 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @kinlab/kin-mcp
|
|
2
|
+
|
|
3
|
+
`@kinlab/kin-mcp` is the npm-friendly launcher for Kin's MCP server (the installed
|
|
4
|
+
command is still `kin-mcp`).
|
|
5
|
+
|
|
6
|
+
It downloads the matching Kin release archive from GitHub, verifies the published
|
|
7
|
+
SHA-256 checksum, extracts the `kin` binary into a local cache, and runs:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
kin mcp start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Use it directly from an MCP client with `npx`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"kin": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@kinlab/kin-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For a pinned version:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"kin": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "@kinlab/kin-mcp@0.2.0"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Node.js 20+
|
|
44
|
+
- macOS or Linux
|
|
45
|
+
- A Kin-initialized repository (`kin init`)
|
|
46
|
+
|
|
47
|
+
Windows users should run Kin through WSL2 during the alpha.
|
|
48
|
+
|
|
49
|
+
## Cache
|
|
50
|
+
|
|
51
|
+
The wrapper caches the downloaded `kin` binary under:
|
|
52
|
+
|
|
53
|
+
- macOS: `~/Library/Caches/kin-mcp`
|
|
54
|
+
- Linux: `~/.cache/kin-mcp`
|
|
55
|
+
|
|
56
|
+
Set `KIN_MCP_CACHE_DIR` to override the cache location.
|
|
57
|
+
|
|
58
|
+
## Environment
|
|
59
|
+
|
|
60
|
+
- `KIN_MCP_KIN_BINARY`: run a specific local `kin` binary instead of downloading one
|
|
61
|
+
- `KIN_BINARY_PATH`: alias for `KIN_MCP_KIN_BINARY`
|
|
62
|
+
- `KIN_MCP_CACHE_DIR`: override the cache directory
|
|
63
|
+
- `KIN_MCP_AUTO_INIT=1`: allow the wrapper to run `kin init .` when `.kin/` is missing
|
|
64
|
+
- `KIN_MCP_RELEASE_BASE_URL`: override the GitHub release download base URL
|
|
65
|
+
|
|
66
|
+
## Local Check
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
npx -y @kinlab/kin-mcp --print-bin
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then initialize a repository and let the MCP client launch `kin-mcp`.
|
package/bin/kin-mcp.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kinlab/kin-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Start Kin's MCP server through an npm-friendly wrapper.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kin-mcp": "./bin/kin-mcp.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"package.json"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test",
|
|
17
|
+
"lint": "node --check ./bin/kin-mcp.js && node --check ./src/index.js && node --check ./test/index.test.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"kin",
|
|
24
|
+
"mcp",
|
|
25
|
+
"claude",
|
|
26
|
+
"codex",
|
|
27
|
+
"gemini"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/firelock-ai/kin.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/firelock-ai/kin",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/firelock-ai/kin/issues"
|
|
36
|
+
},
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Firelock, LLC
|
|
3
|
+
|
|
4
|
+
import cp from 'node:child_process';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import fsp from 'node:fs/promises';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const packageJson = require('../package.json');
|
|
14
|
+
|
|
15
|
+
export const PACKAGE_VERSION = packageJson.version;
|
|
16
|
+
export const DEFAULT_RELEASE_BASE_URL =
|
|
17
|
+
'https://github.com/firelock-ai/kin/releases/download';
|
|
18
|
+
|
|
19
|
+
export function resolveReleaseTag(version = PACKAGE_VERSION) {
|
|
20
|
+
return version.startsWith('v') ? version : `v${version}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveReleaseAsset(platform = process.platform, arch = process.arch) {
|
|
24
|
+
if (platform === 'darwin' && arch === 'arm64') {
|
|
25
|
+
return {
|
|
26
|
+
assetName: 'kin-macos-aarch64',
|
|
27
|
+
archiveName: 'kin-macos-aarch64.tar.gz',
|
|
28
|
+
binaryName: 'kin'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (platform === 'darwin' && arch === 'x64') {
|
|
32
|
+
return {
|
|
33
|
+
assetName: 'kin-macos-x86_64',
|
|
34
|
+
archiveName: 'kin-macos-x86_64.tar.gz',
|
|
35
|
+
binaryName: 'kin'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (platform === 'linux' && arch === 'x64') {
|
|
39
|
+
return {
|
|
40
|
+
assetName: 'kin-linux-x86_64',
|
|
41
|
+
archiveName: 'kin-linux-x86_64.tar.gz',
|
|
42
|
+
binaryName: 'kin'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (platform === 'linux' && arch === 'arm64') {
|
|
46
|
+
return {
|
|
47
|
+
assetName: 'kin-linux-aarch64',
|
|
48
|
+
archiveName: 'kin-linux-aarch64.tar.gz',
|
|
49
|
+
binaryName: 'kin'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(
|
|
54
|
+
`kin-mcp does not have a published Kin binary for ${platform}/${arch} yet. ` +
|
|
55
|
+
'On Windows, use WSL2 for this alpha.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveCacheRoot(
|
|
60
|
+
env = process.env,
|
|
61
|
+
platform = process.platform,
|
|
62
|
+
homeDir = os.homedir()
|
|
63
|
+
) {
|
|
64
|
+
if (env.KIN_MCP_CACHE_DIR) {
|
|
65
|
+
return path.resolve(env.KIN_MCP_CACHE_DIR);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (platform === 'darwin') {
|
|
69
|
+
return path.join(homeDir, 'Library', 'Caches', 'kin-mcp');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (platform === 'win32') {
|
|
73
|
+
const localAppData = env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
|
|
74
|
+
return path.join(localAppData, 'kin-mcp', 'Cache');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const xdgCacheHome = env.XDG_CACHE_HOME || path.join(homeDir, '.cache');
|
|
78
|
+
return path.join(xdgCacheHome, 'kin-mcp');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function resolveCachedBinaryPath({
|
|
82
|
+
env = process.env,
|
|
83
|
+
platform = process.platform,
|
|
84
|
+
arch = process.arch,
|
|
85
|
+
version = PACKAGE_VERSION,
|
|
86
|
+
homeDir = os.homedir(),
|
|
87
|
+
cacheRoot
|
|
88
|
+
} = {}) {
|
|
89
|
+
const { assetName, binaryName } = resolveReleaseAsset(platform, arch);
|
|
90
|
+
const root = cacheRoot || resolveCacheRoot(env, platform, homeDir);
|
|
91
|
+
return path.join(root, resolveReleaseTag(version), assetName, binaryName);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function ensureKinBinary({
|
|
95
|
+
env = process.env,
|
|
96
|
+
platform = process.platform,
|
|
97
|
+
arch = process.arch,
|
|
98
|
+
version = PACKAGE_VERSION,
|
|
99
|
+
homeDir = os.homedir(),
|
|
100
|
+
cacheRoot
|
|
101
|
+
} = {}) {
|
|
102
|
+
const configuredBinary = env.KIN_MCP_KIN_BINARY || env.KIN_BINARY_PATH;
|
|
103
|
+
if (configuredBinary) {
|
|
104
|
+
const resolved = path.resolve(configuredBinary);
|
|
105
|
+
await assertRunnable(resolved, platform);
|
|
106
|
+
return resolved;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const binaryPath = resolveCachedBinaryPath({
|
|
110
|
+
env,
|
|
111
|
+
platform,
|
|
112
|
+
arch,
|
|
113
|
+
version,
|
|
114
|
+
homeDir,
|
|
115
|
+
cacheRoot
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (await isRunnable(binaryPath, platform)) {
|
|
119
|
+
return binaryPath;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await installKinBinary({ binaryPath, env, platform, arch, version });
|
|
123
|
+
return binaryPath;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function runKinMcp(argv = [], options = {}) {
|
|
127
|
+
const stdout = options.stdout || process.stdout;
|
|
128
|
+
const stderr = options.stderr || process.stderr;
|
|
129
|
+
const env = options.env || process.env;
|
|
130
|
+
|
|
131
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
132
|
+
stdout.write(renderHelp());
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
137
|
+
stdout.write(`${PACKAGE_VERSION}\n`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (argv.includes('--print-bin')) {
|
|
142
|
+
const binaryPath = await ensureKinBinary(options);
|
|
143
|
+
stdout.write(`${binaryPath}\n`);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (argv.length > 0) {
|
|
148
|
+
stderr.write(
|
|
149
|
+
'kin-mcp does not accept subcommands. It always runs `kin mcp start`.\n'
|
|
150
|
+
);
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const binaryPath = await ensureKinBinary(options);
|
|
155
|
+
|
|
156
|
+
const cwd = options.cwd || process.cwd();
|
|
157
|
+
if (!await kinRepoExists(cwd)) {
|
|
158
|
+
if (!isTruthyEnv(env.KIN_MCP_AUTO_INIT)) {
|
|
159
|
+
stderr.write(
|
|
160
|
+
'No .kin/ found. Run `kin init .` first, or set KIN_MCP_AUTO_INIT=1 to allow this wrapper to initialize the repo.\n'
|
|
161
|
+
);
|
|
162
|
+
return 2;
|
|
163
|
+
}
|
|
164
|
+
stderr.write('No .kin/ found; KIN_MCP_AUTO_INIT=1, running kin init...\n');
|
|
165
|
+
const initCode = await spawnKin(binaryPath, ['init', '.'], { ...options, cwd });
|
|
166
|
+
if (initCode !== 0) {
|
|
167
|
+
stderr.write('kin init failed. Cannot start MCP server.\n');
|
|
168
|
+
return initCode;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return spawnKin(binaryPath, ['mcp', 'start'], options);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isTruthyEnv(value) {
|
|
176
|
+
return ['1', 'true', 'TRUE', 'yes', 'YES'].includes(String(value || ''));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function renderHelp() {
|
|
180
|
+
return `kin-mcp ${PACKAGE_VERSION}
|
|
181
|
+
|
|
182
|
+
Usage:
|
|
183
|
+
kin-mcp
|
|
184
|
+
kin-mcp --print-bin
|
|
185
|
+
kin-mcp --version
|
|
186
|
+
|
|
187
|
+
This wrapper downloads a matching Kin release binary on demand, caches it
|
|
188
|
+
locally, and then runs:
|
|
189
|
+
|
|
190
|
+
kin mcp start
|
|
191
|
+
|
|
192
|
+
Environment:
|
|
193
|
+
KIN_MCP_KIN_BINARY Use a specific kin binary
|
|
194
|
+
KIN_BINARY_PATH Alias for KIN_MCP_KIN_BINARY
|
|
195
|
+
KIN_MCP_CACHE_DIR Override the cache directory
|
|
196
|
+
KIN_MCP_AUTO_INIT Set to 1 to allow wrapper-initiated kin init
|
|
197
|
+
KIN_MCP_RELEASE_BASE_URL
|
|
198
|
+
Override the release download base URL
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function installKinBinary({ binaryPath, env, platform, arch, version }) {
|
|
203
|
+
const { assetName, archiveName, binaryName } = resolveReleaseAsset(platform, arch);
|
|
204
|
+
const tag = resolveReleaseTag(version);
|
|
205
|
+
const baseUrl = (env.KIN_MCP_RELEASE_BASE_URL || DEFAULT_RELEASE_BASE_URL).replace(
|
|
206
|
+
/\/$/,
|
|
207
|
+
''
|
|
208
|
+
);
|
|
209
|
+
const archiveUrl = `${baseUrl}/${tag}/${archiveName}`;
|
|
210
|
+
const checksumUrl = `${archiveUrl}.sha256`;
|
|
211
|
+
|
|
212
|
+
await fsp.mkdir(path.dirname(binaryPath), { recursive: true });
|
|
213
|
+
|
|
214
|
+
const checksumText = await fetchText(checksumUrl);
|
|
215
|
+
const expectedSha = parseChecksum(checksumText);
|
|
216
|
+
const archiveBytes = await fetchBytes(archiveUrl);
|
|
217
|
+
const actualSha = sha256(archiveBytes);
|
|
218
|
+
|
|
219
|
+
if (actualSha !== expectedSha) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`checksum mismatch for ${archiveName}: expected ${expectedSha}, got ${actualSha}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await installFromArchive({
|
|
226
|
+
archiveBytes,
|
|
227
|
+
archiveName,
|
|
228
|
+
assetName,
|
|
229
|
+
binaryName,
|
|
230
|
+
binaryPath,
|
|
231
|
+
platform
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function installFromArchive({
|
|
236
|
+
archiveBytes,
|
|
237
|
+
archiveName,
|
|
238
|
+
assetName,
|
|
239
|
+
binaryName,
|
|
240
|
+
binaryPath,
|
|
241
|
+
platform
|
|
242
|
+
}) {
|
|
243
|
+
const tmpRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'kin-mcp-install-'));
|
|
244
|
+
const archivePath = path.join(tmpRoot, archiveName);
|
|
245
|
+
const tmpPath = `${binaryPath}.download`;
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await fsp.writeFile(archivePath, archiveBytes);
|
|
249
|
+
await execFile('tar', ['-xzf', archivePath, '-C', tmpRoot]);
|
|
250
|
+
|
|
251
|
+
const extractedBinary = path.join(tmpRoot, assetName, binaryName);
|
|
252
|
+
await fsp.access(extractedBinary, fs.constants.R_OK);
|
|
253
|
+
await fsp.copyFile(extractedBinary, tmpPath);
|
|
254
|
+
if (platform !== 'win32') {
|
|
255
|
+
await fsp.chmod(tmpPath, 0o755);
|
|
256
|
+
}
|
|
257
|
+
await fsp.rename(tmpPath, binaryPath);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
await fsp.unlink(tmpPath).catch(() => {});
|
|
260
|
+
throw new Error(`failed to install ${binaryName} from ${archiveName}: ${error.message}`);
|
|
261
|
+
} finally {
|
|
262
|
+
await fsp.rm(tmpRoot, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function execFile(file, args, options = {}) {
|
|
267
|
+
return new Promise((resolve, reject) => {
|
|
268
|
+
cp.execFile(file, args, options, (error, stdout, stderr) => {
|
|
269
|
+
if (error) {
|
|
270
|
+
if (stderr) {
|
|
271
|
+
error.message = `${error.message}: ${stderr.trim()}`;
|
|
272
|
+
}
|
|
273
|
+
reject(error);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
resolve({ stdout, stderr });
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function fetchText(url) {
|
|
282
|
+
const response = await fetch(url, {
|
|
283
|
+
headers: { 'user-agent': `kin-mcp/${PACKAGE_VERSION}` },
|
|
284
|
+
signal: AbortSignal.timeout(60_000)
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (!response.ok) {
|
|
288
|
+
throw new Error(`failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return response.text();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function fetchBytes(url) {
|
|
295
|
+
const response = await fetch(url, {
|
|
296
|
+
headers: { 'user-agent': `kin-mcp/${PACKAGE_VERSION}` },
|
|
297
|
+
signal: AbortSignal.timeout(120_000)
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
throw new Error(`failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return Buffer.from(await response.arrayBuffer());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function parseChecksum(text) {
|
|
308
|
+
const match = text.trim().match(/\b([a-fA-F0-9]{64})\b/);
|
|
309
|
+
if (!match) {
|
|
310
|
+
throw new Error('failed to parse SHA256 checksum from release metadata');
|
|
311
|
+
}
|
|
312
|
+
return match[1].toLowerCase();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function sha256(bytes) {
|
|
316
|
+
return crypto.createHash('sha256').update(bytes).digest('hex');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function kinRepoExists(cwd) {
|
|
320
|
+
try {
|
|
321
|
+
const stat = await fsp.stat(path.join(cwd, '.kin'));
|
|
322
|
+
return stat.isDirectory();
|
|
323
|
+
} catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function assertRunnable(filePath, platform) {
|
|
329
|
+
if (!(await isRunnable(filePath, platform))) {
|
|
330
|
+
throw new Error(`kin binary not found or not executable: ${filePath}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function isRunnable(filePath, platform) {
|
|
335
|
+
try {
|
|
336
|
+
const mode = platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK;
|
|
337
|
+
await fsp.access(filePath, mode);
|
|
338
|
+
return true;
|
|
339
|
+
} catch {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function spawnKin(binaryPath, args, options) {
|
|
345
|
+
const env = options.env || process.env;
|
|
346
|
+
|
|
347
|
+
return new Promise((resolve, reject) => {
|
|
348
|
+
const child = cp.spawn(binaryPath, args, {
|
|
349
|
+
cwd: options.cwd || process.cwd(),
|
|
350
|
+
env,
|
|
351
|
+
stdio: options.stdio || 'inherit'
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const handlers = new Map();
|
|
355
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
356
|
+
const handler = () => {
|
|
357
|
+
if (!child.killed) {
|
|
358
|
+
child.kill(signal);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
handlers.set(signal, handler);
|
|
362
|
+
process.on(signal, handler);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const cleanup = () => {
|
|
366
|
+
for (const [signal, handler] of handlers.entries()) {
|
|
367
|
+
process.off(signal, handler);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
child.on('error', error => {
|
|
372
|
+
cleanup();
|
|
373
|
+
reject(error);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
child.on('exit', (code, signal) => {
|
|
377
|
+
cleanup();
|
|
378
|
+
if (signal) {
|
|
379
|
+
resolve(1);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
resolve(code ?? 1);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
}
|