@caido-utils/backend 1.2.0 → 1.5.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 +225 -0
- package/dist/filter/index.d.ts +6 -0
- package/dist/filter/index.js +29 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/request.d.ts +16 -0
- package/dist/http/request.js +77 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/requests/headers.d.ts +6 -0
- package/dist/requests/headers.js +17 -0
- package/dist/requests/index.d.ts +1 -0
- package/dist/requests/index.js +1 -0
- package/package.json +4 -1
- package/dist/process/index.d.ts +0 -1
- package/dist/process/index.js +0 -1
- package/dist/process/spawn.d.ts +0 -2
- package/dist/process/spawn.js +0 -58
package/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# @caido-utils/backend
|
|
2
|
+
|
|
3
|
+
Backend utilities for Caido plugins.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @caido-utils/backend
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Peer dependency:** `@caido/sdk-backend`
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { queryRequests, resolveFilterQuery, Queue } from "@caido-utils/backend";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Requests
|
|
20
|
+
|
|
21
|
+
### queryRequests
|
|
22
|
+
|
|
23
|
+
Paginated query over all requests with optional filtering, deduplication, and scope checking.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const items = await queryRequests(sdk, {
|
|
27
|
+
filter: 'req.host.cont:"example.com"',
|
|
28
|
+
excludeStaticAssets: true,
|
|
29
|
+
deduplicate: true,
|
|
30
|
+
inScope: true,
|
|
31
|
+
onPage: (pageItems) => console.log(`Got ${pageItems.length} items`),
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Options
|
|
36
|
+
|
|
37
|
+
| Option | Type | Default | Description |
|
|
38
|
+
|--------|------|---------|-------------|
|
|
39
|
+
| `filter` | `string` | - | HTTPQL filter string |
|
|
40
|
+
| `pageSize` | `number` | `1000` | Items per page |
|
|
41
|
+
| `excludeStaticAssets` | `boolean` | `false` | Filter out images, fonts, JS, CSS, media |
|
|
42
|
+
| `deduplicate` | `boolean` | `false` | Keep last occurrence per `method\|host\|path` |
|
|
43
|
+
| `inScope` | `boolean` | `false` | Post-filter using `sdk.requests.inScope()` |
|
|
44
|
+
| `onPage` | `(items[]) => void` | - | Callback after each page |
|
|
45
|
+
|
|
46
|
+
Always applies base filter `resp.roundtrip.gt:0` (only requests with responses).
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Fetch
|
|
51
|
+
|
|
52
|
+
Concurrent HTTP request sender with configurable parallelism and delay.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const fetcher = new Fetch(sdk, { threads: 5, delay: 100 });
|
|
56
|
+
const response = await fetcher.send(requestSpec);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Constructor Options
|
|
60
|
+
|
|
61
|
+
| Option | Type | Default | Description |
|
|
62
|
+
|--------|------|---------|-------------|
|
|
63
|
+
| `threads` | `number` | `10` | Max concurrent requests |
|
|
64
|
+
| `delay` | `number` | `0` | Delay in ms after each request completes |
|
|
65
|
+
|
|
66
|
+
#### Methods
|
|
67
|
+
|
|
68
|
+
| Method | Signature | Description |
|
|
69
|
+
|--------|-----------|-------------|
|
|
70
|
+
| `send` | `(request: RequestSpec \| RequestSpecRaw) => Promise<RequestResponse>` | Queue and send a request |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Filter
|
|
75
|
+
|
|
76
|
+
### resolveFilterQuery
|
|
77
|
+
|
|
78
|
+
Resolves custom `filter:` tokens into real HTTPQL.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const resolved = resolveFilterQuery('filter:1hr AND req.host.cont:"example.com"');
|
|
82
|
+
// => 'req.created_at.gt:"2026-02-16 15:00:00" AND req.host.cont:"example.com"'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Token Mapping
|
|
86
|
+
|
|
87
|
+
| Token | Expands To |
|
|
88
|
+
|-------|------------|
|
|
89
|
+
| `filter:inscope` | `preset:inscope` |
|
|
90
|
+
| `filter:recent` | `req.created_at.gt:"<15 minutes ago>"` |
|
|
91
|
+
| `filter:1hr` | `req.created_at.gt:"<1 hour ago>"` |
|
|
92
|
+
| `filter:6hr` | `req.created_at.gt:"<6 hours ago>"` |
|
|
93
|
+
| `filter:12hr` | `req.created_at.gt:"<12 hours ago>"` |
|
|
94
|
+
| `filter:24hr` | `req.created_at.gt:"<24 hours ago>"` |
|
|
95
|
+
| `filter:<unknown>` | Removed |
|
|
96
|
+
|
|
97
|
+
Timestamps use local time in `YYYY-MM-DD HH:MM:SS` format.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Pool
|
|
102
|
+
|
|
103
|
+
### Queue
|
|
104
|
+
|
|
105
|
+
Concurrent async task queue with pause/resume/stop.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const queue = new Queue<string>(
|
|
109
|
+
{ concurrency: 5, delayMs: 100 },
|
|
110
|
+
async (url) => {
|
|
111
|
+
await processUrl(url);
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
onError: (url, error) => console.error(`Failed: ${url}`, error),
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
queue.enqueue(["https://a.com", "https://b.com"]);
|
|
119
|
+
queue.pause();
|
|
120
|
+
queue.resume();
|
|
121
|
+
queue.stop();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Config
|
|
125
|
+
|
|
126
|
+
| Option | Type | Default | Description |
|
|
127
|
+
|--------|------|---------|-------------|
|
|
128
|
+
| `concurrency` | `number` | required | Max parallel workers |
|
|
129
|
+
| `delayMs` | `number` | `0` | Delay between items per worker |
|
|
130
|
+
|
|
131
|
+
#### Callbacks
|
|
132
|
+
|
|
133
|
+
| Callback | Signature | Description |
|
|
134
|
+
|----------|-----------|-------------|
|
|
135
|
+
| `onError` | `(item: T, error: string) => void` | Called when a task throws |
|
|
136
|
+
|
|
137
|
+
#### Methods
|
|
138
|
+
|
|
139
|
+
| Method | Signature | Description |
|
|
140
|
+
|--------|-----------|-------------|
|
|
141
|
+
| `enqueue` | `(items: T \| T[]) => void` | Add items to the queue |
|
|
142
|
+
| `pause` | `() => void` | Pause processing |
|
|
143
|
+
| `resume` | `() => void` | Resume processing |
|
|
144
|
+
| `stop` | `() => void` | Stop and clear queue |
|
|
145
|
+
| `isRunning` | `() => boolean` | Check if queue is active |
|
|
146
|
+
| `isPaused` | `() => boolean` | Check if paused |
|
|
147
|
+
| `setConcurrency` | `(n: number) => void` | Change max workers at runtime |
|
|
148
|
+
| `pending` | `number` | Items waiting |
|
|
149
|
+
| `completed` | `number` | Items processed |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Crypto
|
|
154
|
+
|
|
155
|
+
### sha256
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const hash = sha256("content");
|
|
159
|
+
// => "ed7002b439e9ac845f22357d822bac..."
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Returns hex-encoded SHA-256 hash.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Process
|
|
167
|
+
|
|
168
|
+
### spawnCommand
|
|
169
|
+
|
|
170
|
+
Run a binary bundled in the plugin's assets directory.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const output = await spawnCommand(sdk, "my-tool", ["--flag", "value"], stdinData);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Arg | Type | Description |
|
|
177
|
+
|-----|------|-------------|
|
|
178
|
+
| `sdk` | `SDK` | Backend SDK instance |
|
|
179
|
+
| `binary` | `string` | Filename in `sdk.meta.assetsPath()` |
|
|
180
|
+
| `args` | `string[]` | Command-line arguments |
|
|
181
|
+
| `stdin` | `string` | Optional stdin input |
|
|
182
|
+
|
|
183
|
+
Returns stdout as a string. Throws on non-zero exit code. Automatically sets executable permissions if needed.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Project
|
|
188
|
+
|
|
189
|
+
### getProjectId
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const projectId = await getProjectId(sdk);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Returns the current Caido project ID. Throws `"Project not found"` if no project is active.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Storage
|
|
200
|
+
|
|
201
|
+
File I/O within the plugin's data directory.
|
|
202
|
+
|
|
203
|
+
### storeFile
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const filePath = await storeFile(sdk, {
|
|
207
|
+
data: responseBody,
|
|
208
|
+
contentType: "application/json", // determines file extension
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| Option | Type | Description |
|
|
213
|
+
|--------|------|-------------|
|
|
214
|
+
| `data` | `string` | File content |
|
|
215
|
+
| `contentType` | `string` | Optional — determines file extension |
|
|
216
|
+
|
|
217
|
+
Returns the absolute file path. Supported extensions: `.js`, `.html`, `.json`, `.xml`, `.css`, `.txt`, `.ts`.
|
|
218
|
+
|
|
219
|
+
### readFile / deleteFile / fileExists
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const content = await readFile(filePath);
|
|
223
|
+
await deleteFile(filePath);
|
|
224
|
+
const exists = await fileExists(filePath);
|
|
225
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const TIME_FILTERS = {
|
|
2
|
+
recent: 15 * 60 * 1e3,
|
|
3
|
+
"1hr": 60 * 60 * 1e3,
|
|
4
|
+
"6hr": 6 * 60 * 60 * 1e3,
|
|
5
|
+
"12hr": 12 * 60 * 60 * 1e3,
|
|
6
|
+
"24hr": 24 * 60 * 60 * 1e3
|
|
7
|
+
};
|
|
8
|
+
function formatDateTime(date) {
|
|
9
|
+
const y = date.getFullYear();
|
|
10
|
+
const mo = String(date.getMonth() + 1).padStart(2, "0");
|
|
11
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
12
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
13
|
+
const mi = String(date.getMinutes()).padStart(2, "0");
|
|
14
|
+
const s = String(date.getSeconds()).padStart(2, "0");
|
|
15
|
+
return `${y}-${mo}-${d} ${h}:${mi}:${s}`;
|
|
16
|
+
}
|
|
17
|
+
export function resolveFilterQuery(query) {
|
|
18
|
+
return query.replace(/filter:(\w+)/g, (_, value) => {
|
|
19
|
+
const ms = TIME_FILTERS[value];
|
|
20
|
+
if (ms !== void 0) {
|
|
21
|
+
const cutoff = new Date(Date.now() - ms);
|
|
22
|
+
return `req.created_at.gt:"${formatDateTime(cutoff)}"`;
|
|
23
|
+
}
|
|
24
|
+
if (value === "inscope") {
|
|
25
|
+
return "preset:inscope";
|
|
26
|
+
}
|
|
27
|
+
return "";
|
|
28
|
+
}).replace(/\s{2,}/g, " ").trim();
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HttpRequest, type HttpRequestOptions } from "./request";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HttpRequest } from "./request.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SDK } from "caido:plugin";
|
|
2
|
+
export type HttpRequestOptions = {
|
|
3
|
+
url: string;
|
|
4
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
|
|
5
|
+
headers?: Record<string, string> | Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}>;
|
|
9
|
+
body?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class HttpRequest {
|
|
12
|
+
private sdk;
|
|
13
|
+
private maxRedirects;
|
|
14
|
+
constructor(sdk: SDK, maxRedirects?: number);
|
|
15
|
+
send(options: HttpRequestOptions): Promise<any>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { RequestSpec } from "caido:utils";
|
|
2
|
+
import parse from "url-parse";
|
|
3
|
+
const DEFAULT_HEADERS = {
|
|
4
|
+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
|
|
5
|
+
"Sec-Fetch-Site": "same-origin",
|
|
6
|
+
"Sec-Fetch-Mode": "navigate",
|
|
7
|
+
"Sec-Fetch-User": "?1",
|
|
8
|
+
"Sec-Fetch-Dest": "document"
|
|
9
|
+
};
|
|
10
|
+
function normalizeHeaders(headers) {
|
|
11
|
+
if (headers === void 0) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(headers)) {
|
|
15
|
+
return headers.reduce(
|
|
16
|
+
(acc, { name, value }) => {
|
|
17
|
+
acc[name] = value;
|
|
18
|
+
return acc;
|
|
19
|
+
},
|
|
20
|
+
{}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
function resolveRedirectUrl(currentUrl, location) {
|
|
26
|
+
if (location.startsWith("http://") || location.startsWith("https://")) {
|
|
27
|
+
return location;
|
|
28
|
+
}
|
|
29
|
+
const parsed = parse(currentUrl);
|
|
30
|
+
if (location.startsWith("/")) {
|
|
31
|
+
return `${parsed.protocol}//${parsed.host}${location}`;
|
|
32
|
+
}
|
|
33
|
+
const basePath = parsed.pathname.substring(
|
|
34
|
+
0,
|
|
35
|
+
parsed.pathname.lastIndexOf("/") + 1
|
|
36
|
+
);
|
|
37
|
+
return `${parsed.protocol}//${parsed.host}${basePath}${location}`;
|
|
38
|
+
}
|
|
39
|
+
export class HttpRequest {
|
|
40
|
+
sdk;
|
|
41
|
+
maxRedirects;
|
|
42
|
+
constructor(sdk, maxRedirects = 5) {
|
|
43
|
+
this.sdk = sdk;
|
|
44
|
+
this.maxRedirects = maxRedirects;
|
|
45
|
+
}
|
|
46
|
+
async send(options) {
|
|
47
|
+
let currentUrl = options.url;
|
|
48
|
+
let redirectCount = 0;
|
|
49
|
+
while (redirectCount <= this.maxRedirects) {
|
|
50
|
+
const spec = new RequestSpec(currentUrl);
|
|
51
|
+
if (options.method !== void 0) {
|
|
52
|
+
spec.setMethod(options.method);
|
|
53
|
+
}
|
|
54
|
+
const normalizedHeaders = normalizeHeaders(options.headers);
|
|
55
|
+
const headers = { ...DEFAULT_HEADERS, ...normalizedHeaders };
|
|
56
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
57
|
+
spec.setHeader(name, value);
|
|
58
|
+
}
|
|
59
|
+
if (options.body !== void 0) {
|
|
60
|
+
spec.setBody(options.body);
|
|
61
|
+
}
|
|
62
|
+
const result = await this.sdk.requests.send(spec);
|
|
63
|
+
const statusCode = result.response.getCode();
|
|
64
|
+
if ([301, 302, 303, 307, 308].includes(statusCode)) {
|
|
65
|
+
const locationHeader = result.response.getHeader("Location");
|
|
66
|
+
if (!locationHeader) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
currentUrl = resolveRedirectUrl(currentUrl, locationHeader.toString());
|
|
70
|
+
redirectCount++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Too many redirects (max: ${this.maxRedirects})`);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export { spawnCommand } from "@caido-utils/shared";
|
|
1
2
|
export { sha256 } from "./crypto";
|
|
3
|
+
export { resolveFilterQuery } from "./filter";
|
|
4
|
+
export { HttpRequest, type HttpRequestOptions } from "./http";
|
|
2
5
|
export { Queue } from "./pool";
|
|
3
6
|
export type { PoolCallbacks, PoolConfig } from "./pool";
|
|
4
|
-
export { spawnCommand } from "./process";
|
|
5
7
|
export { getProjectId } from "./project";
|
|
6
|
-
export { Fetch, queryRequests, type FetchOptions, type QueryRequestsOptions, } from "./requests";
|
|
8
|
+
export { Fetch, filterAuthHeaders, queryRequests, type FetchOptions, type QueryRequestsOptions, } from "./requests";
|
|
7
9
|
export { deleteFile, fileExists, readFile, storeFile } from "./storage";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
export { spawnCommand } from "@caido-utils/shared";
|
|
1
2
|
export { sha256 } from "./crypto/index.js";
|
|
3
|
+
export { resolveFilterQuery } from "./filter/index.js";
|
|
4
|
+
export { HttpRequest } from "./http/index.js";
|
|
2
5
|
export { Queue } from "./pool/index.js";
|
|
3
|
-
export { spawnCommand } from "./process/index.js";
|
|
4
6
|
export { getProjectId } from "./project/index.js";
|
|
5
7
|
export {
|
|
6
8
|
Fetch,
|
|
9
|
+
filterAuthHeaders,
|
|
7
10
|
queryRequests
|
|
8
11
|
} from "./requests/index.js";
|
|
9
12
|
export { deleteFile, fileExists, readFile, storeFile } from "./storage/index.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const AUTH_HEADERS = [
|
|
2
|
+
"authorization",
|
|
3
|
+
"x-api-key",
|
|
4
|
+
"x-auth-token",
|
|
5
|
+
"x-access-token",
|
|
6
|
+
"x-csrf-token",
|
|
7
|
+
"x-xsrf-token"
|
|
8
|
+
];
|
|
9
|
+
export function filterAuthHeaders(headers) {
|
|
10
|
+
const result = [];
|
|
11
|
+
for (const { name, value } of headers) {
|
|
12
|
+
if (AUTH_HEADERS.includes(name.toLowerCase())) {
|
|
13
|
+
result.push(`${name}: ${value}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
package/dist/requests/index.d.ts
CHANGED
package/dist/requests/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caido-utils/backend",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "unbuild"
|
|
16
16
|
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@caido-utils/shared": "workspace:*"
|
|
19
|
+
},
|
|
17
20
|
"peerDependencies": {
|
|
18
21
|
"@caido/sdk-backend": ">=0.46.0"
|
|
19
22
|
},
|
package/dist/process/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { spawnCommand } from "./spawn";
|
package/dist/process/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { spawnCommand } from "./spawn.js";
|
package/dist/process/spawn.d.ts
DELETED
package/dist/process/spawn.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
spawn as nodeSpawn
|
|
3
|
-
} from "child_process";
|
|
4
|
-
import { access, constants } from "fs/promises";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
async function ensureBinaryExecutable(binaryPath, sdk) {
|
|
7
|
-
try {
|
|
8
|
-
await access(binaryPath, constants.F_OK | constants.X_OK);
|
|
9
|
-
} catch {
|
|
10
|
-
try {
|
|
11
|
-
await makeExecutable(binaryPath);
|
|
12
|
-
} catch (chmodError) {
|
|
13
|
-
sdk.console.error(
|
|
14
|
-
`Failed to make ${binaryPath} executable: ${chmodError}`
|
|
15
|
-
);
|
|
16
|
-
throw new Error(`Cannot make binary executable: ${chmodError}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
async function makeExecutable(binaryPath) {
|
|
21
|
-
const child = nodeSpawn("chmod", ["+x", binaryPath], {
|
|
22
|
-
shell: true
|
|
23
|
-
});
|
|
24
|
-
await driveChild(child);
|
|
25
|
-
}
|
|
26
|
-
export async function spawnCommand(sdk, binary, args, stdin) {
|
|
27
|
-
try {
|
|
28
|
-
const binaryPath = path.join(sdk.meta.assetsPath(), binary);
|
|
29
|
-
await ensureBinaryExecutable(binaryPath, sdk);
|
|
30
|
-
const child = nodeSpawn(binaryPath, args);
|
|
31
|
-
if (stdin !== void 0) {
|
|
32
|
-
child.stdin.write(stdin);
|
|
33
|
-
child.stdin.end();
|
|
34
|
-
}
|
|
35
|
-
const output = await driveChild(child);
|
|
36
|
-
return output;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
sdk.console.error(`Spawn command error (${binary}): ${error}`);
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function driveChild(child, allowExitCode1 = false) {
|
|
43
|
-
let output = "";
|
|
44
|
-
child.stdout.on("data", (data) => {
|
|
45
|
-
output += data.toString();
|
|
46
|
-
});
|
|
47
|
-
let error = "";
|
|
48
|
-
child.stderr.on("data", (data) => {
|
|
49
|
-
error += data.toString();
|
|
50
|
-
});
|
|
51
|
-
const exitCode = await new Promise((resolve) => {
|
|
52
|
-
child.on("close", resolve);
|
|
53
|
-
});
|
|
54
|
-
if (exitCode !== void 0 && exitCode !== 0 && !(allowExitCode1 && exitCode === 1)) {
|
|
55
|
-
throw new Error(`subprocess error exit ${exitCode}, ${error}`);
|
|
56
|
-
}
|
|
57
|
-
return output;
|
|
58
|
-
}
|