@yume-chan/android-bin 0.0.20 → 0.0.22

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.
Files changed (76) hide show
  1. package/CHANGELOG.json +54 -0
  2. package/CHANGELOG.md +25 -1
  3. package/LICENSE +21 -0
  4. package/README.md +40 -14
  5. package/esm/am.d.ts +17 -0
  6. package/esm/am.d.ts.map +1 -0
  7. package/esm/am.js +42 -0
  8. package/esm/am.js.map +1 -0
  9. package/esm/bu.d.ts +20 -7
  10. package/esm/bu.d.ts.map +1 -1
  11. package/esm/bu.js +47 -8
  12. package/esm/bu.js.map +1 -1
  13. package/esm/bug-report.d.ts +82 -21
  14. package/esm/bug-report.d.ts.map +1 -1
  15. package/esm/bug-report.js +187 -53
  16. package/esm/bug-report.js.map +1 -1
  17. package/esm/cmd.d.ts +4 -6
  18. package/esm/cmd.d.ts.map +1 -1
  19. package/esm/cmd.js +36 -21
  20. package/esm/cmd.js.map +1 -1
  21. package/esm/demo-mode.d.ts +3 -3
  22. package/esm/demo-mode.d.ts.map +1 -1
  23. package/esm/demo-mode.js +14 -15
  24. package/esm/demo-mode.js.map +1 -1
  25. package/esm/dumpsys.d.ts +39 -0
  26. package/esm/dumpsys.d.ts.map +1 -1
  27. package/esm/dumpsys.js +111 -0
  28. package/esm/dumpsys.js.map +1 -1
  29. package/esm/index.d.ts +6 -0
  30. package/esm/index.d.ts.map +1 -1
  31. package/esm/index.js +6 -0
  32. package/esm/index.js.map +1 -1
  33. package/esm/intent.d.ts +10 -0
  34. package/esm/intent.d.ts.map +1 -0
  35. package/esm/intent.js +51 -0
  36. package/esm/intent.js.map +1 -0
  37. package/esm/logcat.d.ts +1 -0
  38. package/esm/logcat.d.ts.map +1 -1
  39. package/esm/logcat.js +20 -18
  40. package/esm/logcat.js.map +1 -1
  41. package/esm/overlay-display.d.ts +20 -3
  42. package/esm/overlay-display.d.ts.map +1 -1
  43. package/esm/overlay-display.js +34 -61
  44. package/esm/overlay-display.js.map +1 -1
  45. package/esm/pm.d.ts +42 -5
  46. package/esm/pm.d.ts.map +1 -1
  47. package/esm/pm.js +164 -46
  48. package/esm/pm.js.map +1 -1
  49. package/esm/settings.d.ts +22 -8
  50. package/esm/settings.d.ts.map +1 -1
  51. package/esm/settings.js +50 -25
  52. package/esm/settings.js.map +1 -1
  53. package/esm/string-format.d.ts +40 -0
  54. package/esm/string-format.d.ts.map +1 -0
  55. package/esm/string-format.js +153 -0
  56. package/esm/string-format.js.map +1 -0
  57. package/esm/utils.d.ts +4 -0
  58. package/esm/utils.d.ts.map +1 -0
  59. package/esm/utils.js +23 -0
  60. package/esm/utils.js.map +1 -0
  61. package/package.json +13 -8
  62. package/src/am.ts +69 -0
  63. package/src/bu.ts +68 -14
  64. package/src/bug-report.ts +234 -68
  65. package/src/cmd.ts +64 -30
  66. package/src/demo-mode.ts +47 -43
  67. package/src/dumpsys.ts +129 -0
  68. package/src/index.ts +6 -0
  69. package/src/intent.ts +63 -0
  70. package/src/logcat.ts +45 -42
  71. package/src/overlay-display.ts +82 -85
  72. package/src/pm.ts +274 -61
  73. package/src/settings.ts +94 -43
  74. package/src/string-format.ts +199 -0
  75. package/src/utils.ts +29 -0
  76. package/tsconfig.build.tsbuildinfo +1 -1
package/src/bug-report.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  // cspell: ignore bugreport
2
2
  // cspell: ignore bugreportz
3
3
 
4
+ import type { Adb, AdbSync } from "@yume-chan/adb";
4
5
  import { AdbCommandBase, AdbSubprocessShellProtocol } from "@yume-chan/adb";
5
- import type { ReadableStream } from "@yume-chan/stream-extra";
6
+ import type { AbortSignal, ReadableStream } from "@yume-chan/stream-extra";
6
7
  import {
8
+ AbortController,
7
9
  DecodeUtf8Stream,
8
10
  PushReadableStream,
9
11
  SplitStringStream,
@@ -11,80 +13,200 @@ import {
11
13
  WritableStream,
12
14
  } from "@yume-chan/stream-extra";
13
15
 
14
- export interface BugReportZVersion {
15
- major: number;
16
- minor: number;
16
+ export interface BugReportCapabilities {
17
+ supportsBugReport: boolean;
18
+ bugReportZVersion?: string | undefined;
19
+ supportsBugReportZ: boolean;
20
+ supportsBugReportZProgress: boolean;
21
+ supportsBugReportZStream: boolean;
22
+ }
17
23
 
18
- supportProgress: boolean;
19
- supportStream: boolean;
24
+ export interface BugReportZOptions {
25
+ signal?: AbortSignal;
26
+ /**
27
+ * A callback that will be called when progress is updated.
28
+ *
29
+ * Specify `onProgress` when `supportsBugReportZProgress` is `false` will throw an error.
30
+ */
31
+ onProgress?: ((completed: string, total: string) => void) | undefined;
20
32
  }
21
33
 
22
- export class BugReportZ extends AdbCommandBase {
23
- public static VERSION_REGEX = /(\d+)\.(\d+)/;
34
+ export class BugReport extends AdbCommandBase {
35
+ static VERSION_REGEX = /(\d+)\.(\d+)/;
24
36
 
25
- public static BEGIN_REGEX = /BEGIN:(.*)/;
37
+ static BEGIN_REGEX = /BEGIN:(.*)/;
26
38
 
27
- public static PROGRESS_REGEX = /PROGRESS:(.*)\/(.*)/;
39
+ static PROGRESS_REGEX = /PROGRESS:(.*)\/(.*)/;
28
40
 
29
- public static OK_REGEX = /OK:(.*)/;
41
+ static OK_REGEX = /OK:(.*)/;
30
42
 
31
- public static FAIL_REGEX = /FAIL:(.*)/;
43
+ static FAIL_REGEX = /FAIL:(.*)/;
32
44
 
33
45
  /**
34
- * Retrieve the version of bugreportz.
35
- *
36
- * @returns a `BugReportVersion` object, or `undefined` if `bugreportz` is not available.
46
+ * Queries the device's bugreport capabilities.
37
47
  */
38
- public async version(): Promise<BugReportZVersion | undefined> {
48
+ static async queryCapabilities(adb: Adb): Promise<BugReport> {
39
49
  // bugreportz requires shell protocol
40
- if (!AdbSubprocessShellProtocol.isSupported(this.adb)) {
41
- return undefined;
50
+ if (!AdbSubprocessShellProtocol.isSupported(adb)) {
51
+ return new BugReport(adb, {
52
+ supportsBugReport: true,
53
+ bugReportZVersion: undefined,
54
+ supportsBugReportZ: false,
55
+ supportsBugReportZProgress: false,
56
+ supportsBugReportZStream: false,
57
+ });
42
58
  }
43
59
 
44
- const { stderr, exitCode } = await this.adb.subprocess.spawnAndWait([
60
+ const { stderr, exitCode } = await adb.subprocess.spawnAndWait([
45
61
  "bugreportz",
46
62
  "-v",
47
63
  ]);
48
64
  if (exitCode !== 0 || stderr === "") {
49
- return undefined;
65
+ return new BugReport(adb, {
66
+ supportsBugReport: true,
67
+ bugReportZVersion: undefined,
68
+ supportsBugReportZ: false,
69
+ supportsBugReportZProgress: false,
70
+ supportsBugReportZStream: false,
71
+ });
50
72
  }
51
73
 
52
- const match = stderr.match(BugReportZ.VERSION_REGEX);
74
+ const match = stderr.match(BugReport.VERSION_REGEX);
53
75
  if (!match) {
54
- return undefined;
76
+ return new BugReport(adb, {
77
+ supportsBugReport: true,
78
+ bugReportZVersion: undefined,
79
+ supportsBugReportZ: false,
80
+ supportsBugReportZProgress: false,
81
+ supportsBugReportZStream: false,
82
+ });
55
83
  }
56
84
 
57
- const major = parseInt(match[1]!, 10);
58
- const minor = parseInt(match[2]!, 10);
59
- return {
60
- major,
61
- minor,
85
+ const [major, minor] = match[0]
86
+ .split(".")
87
+ .map((x) => parseInt(x, 10)) as [number, number];
88
+ return new BugReport(adb, {
89
+ // Before BugReportZ version 1.2 (Android 12), BugReport was deprecated but still works.
90
+ supportsBugReport: major === 1 && minor <= 1,
91
+ bugReportZVersion: match[0],
92
+ supportsBugReportZ: true,
93
+ supportsBugReportZProgress: major > 1 || minor >= 1,
94
+ supportsBugReportZStream: major > 1 || minor >= 2,
95
+ });
96
+ }
97
+
98
+ #supportsBugReport: boolean;
99
+ /**
100
+ * Gets whether the device supports flat (text file, non-zipped) bugreport.
101
+ *
102
+ * Should be `true` for Android version <= 11.
103
+ */
104
+ get supportsBugReport() {
105
+ return this.#supportsBugReport;
106
+ }
62
107
 
63
- supportProgress: this.supportProgress(major, minor),
64
- supportStream: this.supportStream(major, minor),
65
- };
108
+ #bugReportZVersion: string | undefined;
109
+ /**
110
+ * Gets the version of BugReportZ.
111
+ *
112
+ * Will be `undefined` if BugReportZ is not supported.
113
+ */
114
+ get bugReportZVersion() {
115
+ return this.#bugReportZVersion;
66
116
  }
67
117
 
68
- public supportProgress(major: number, minor: number): boolean {
69
- return major > 1 || minor >= 1;
118
+ #supportsBugReportZ: boolean;
119
+ /**
120
+ * Gets whether the device supports zipped bugreport.
121
+ *
122
+ * Should be `true` for Android version >= 7.
123
+ */
124
+ get supportsBugReportZ() {
125
+ return this.#supportsBugReportZ;
70
126
  }
71
127
 
128
+ #supportsBugReportZProgress: boolean;
72
129
  /**
73
- * Create a zipped bugreport file.
130
+ * Gets whether the device supports progress report for zipped bugreport.
74
131
  *
75
- * Compare to `stream`, this method will write the output to a file on device.
132
+ * Should be `true` for Android version >= 8.
133
+ */
134
+ get supportsBugReportZProgress() {
135
+ return this.#supportsBugReportZProgress;
136
+ }
137
+
138
+ #supportsBugReportZStream: boolean;
139
+ /**
140
+ * Gets whether the device supports streaming zipped bugreport.
141
+ *
142
+ * Should be `true` for Android version >= 12.
143
+ */
144
+ get supportsBugReportZStream() {
145
+ return this.#supportsBugReportZStream;
146
+ }
147
+
148
+ constructor(adb: Adb, capabilities: BugReportCapabilities) {
149
+ super(adb);
150
+
151
+ this.#supportsBugReport = capabilities.supportsBugReport;
152
+ this.#bugReportZVersion = capabilities.bugReportZVersion;
153
+ this.#supportsBugReportZ = capabilities.supportsBugReportZ;
154
+ this.#supportsBugReportZProgress =
155
+ capabilities.supportsBugReportZProgress;
156
+ this.#supportsBugReportZStream = capabilities.supportsBugReportZStream;
157
+ }
158
+
159
+ /**
160
+ * Creates a legacy, non-zipped bugreport file, or throws an error if `supportsBugReport` is `false`.
161
+ *
162
+ * @returns A flat (text file, non-zipped) bugreport.
163
+ */
164
+ bugReport(): ReadableStream<Uint8Array> {
165
+ if (!this.#supportsBugReport) {
166
+ throw new Error(
167
+ "Flat (text file, non-zipped) bugreport is not supported.",
168
+ );
169
+ }
170
+
171
+ return new WrapReadableStream(async () => {
172
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
173
+ const process = await this.adb.subprocess.spawn(["bugreport"]);
174
+ return process.stdout;
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Creates a zipped bugreport file, or throws an error if `supportsBugReportZ` is `false`.
180
+ *
181
+ * Compare to `bugReportZStream`, this method will write the output to a file on device.
76
182
  * You can pull it later using sync protocol.
77
183
  *
78
- * @param onProgress Progress callback. Only specify this if `supportsProgress` is `true`.
79
- * @returns The path of the bugreport file.
184
+ * @returns The path to the generated bugreport file on device filesystem.
80
185
  */
81
- public async generate(
82
- onProgress?: (progress: string, total: string) => void
83
- ): Promise<string> {
84
- const process = await this.adb.subprocess.spawn([
85
- "bugreportz",
86
- ...(onProgress ? ["-p"] : []),
87
- ]);
186
+ async bugReportZ(options?: BugReportZOptions): Promise<string> {
187
+ if (options?.signal?.aborted) {
188
+ throw options?.signal.reason ?? new Error("Aborted");
189
+ }
190
+
191
+ if (!this.#supportsBugReportZ) {
192
+ throw new Error("bugreportz is not supported");
193
+ }
194
+
195
+ const args = ["bugreportz"];
196
+ if (options?.onProgress) {
197
+ if (!this.#supportsBugReportZProgress) {
198
+ throw new Error("bugreportz progress is not supported");
199
+ }
200
+ args.push("-p");
201
+ }
202
+
203
+ const process = await this.adb.subprocess.spawn(args, {
204
+ protocols: [AdbSubprocessShellProtocol],
205
+ });
206
+
207
+ options?.signal?.addEventListener("abort", () => {
208
+ void process.kill();
209
+ });
88
210
 
89
211
  let filename: string | undefined;
90
212
  let error: string | undefined;
@@ -96,29 +218,29 @@ export class BugReportZ extends AdbCommandBase {
96
218
  new WritableStream<string>({
97
219
  write(line) {
98
220
  // `BEGIN:` and `PROGRESS:` only appear when `-p` is specified.
99
- let match = line.match(BugReportZ.PROGRESS_REGEX);
221
+ let match = line.match(BugReport.PROGRESS_REGEX);
100
222
  if (match) {
101
- onProgress?.(match[1]!, match[2]!);
223
+ options?.onProgress?.(match[1]!, match[2]!);
102
224
  }
103
225
 
104
- match = line.match(BugReportZ.BEGIN_REGEX);
226
+ match = line.match(BugReport.BEGIN_REGEX);
105
227
  if (match) {
106
228
  filename = match[1]!;
107
229
  }
108
230
 
109
- match = line.match(BugReportZ.OK_REGEX);
231
+ match = line.match(BugReport.OK_REGEX);
110
232
  if (match) {
111
233
  filename = match[1];
112
234
  }
113
235
 
114
- match = line.match(BugReportZ.FAIL_REGEX);
236
+ match = line.match(BugReport.FAIL_REGEX);
115
237
  if (match) {
116
238
  // Don't report error now
117
239
  // We want to gather all output.
118
240
  error = match[1];
119
241
  }
120
242
  },
121
- })
243
+ }),
122
244
  );
123
245
 
124
246
  if (error) {
@@ -133,23 +255,24 @@ export class BugReportZ extends AdbCommandBase {
133
255
  return filename;
134
256
  }
135
257
 
136
- public supportStream(major: number, minor: number): boolean {
137
- return major > 1 || minor >= 2;
138
- }
139
-
140
- public stream(): ReadableStream<Uint8Array> {
258
+ /**
259
+ * Creates a zipped bugreport file, or throws an error if `supportsBugReportZStream` is `false`.
260
+ *
261
+ * @returns The content of the generated bugreport file.
262
+ */
263
+ bugReportZStream(): ReadableStream<Uint8Array> {
141
264
  return new PushReadableStream(async (controller) => {
142
- const process = await this.adb.subprocess.spawn([
143
- "bugreportz",
144
- "-s",
145
- ]);
265
+ const process = await this.adb.subprocess.spawn(
266
+ ["bugreportz", "-s"],
267
+ { protocols: [AdbSubprocessShellProtocol] },
268
+ );
146
269
  process.stdout
147
270
  .pipeTo(
148
271
  new WritableStream({
149
272
  async write(chunk) {
150
273
  await controller.enqueue(chunk);
151
274
  },
152
- })
275
+ }),
153
276
  )
154
277
  .catch((e) => {
155
278
  controller.error(e);
@@ -161,7 +284,7 @@ export class BugReportZ extends AdbCommandBase {
161
284
  write(chunk) {
162
285
  controller.error(new Error(chunk));
163
286
  },
164
- })
287
+ }),
165
288
  )
166
289
  .catch((e) => {
167
290
  controller.error(e);
@@ -169,14 +292,57 @@ export class BugReportZ extends AdbCommandBase {
169
292
  await process.exit;
170
293
  });
171
294
  }
172
- }
173
295
 
174
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
175
- export class BugReport extends AdbCommandBase {
176
- public generate(): ReadableStream<Uint8Array> {
177
- return new WrapReadableStream(async () => {
178
- const process = await this.adb.subprocess.spawn(["bugreport"]);
179
- return process.stdout;
180
- });
296
+ /**
297
+ * Automatically choose the best bugreport method.
298
+ *
299
+ * * If `supportsBugReportZStream` is `true`, this method will return a stream of zipped bugreport.
300
+ * * If `supportsBugReportZ` is `true`, this method will return a stream of zipped bugreport, and will delete the file on device after the stream is closed.
301
+ * * If `supportsBugReport` is `true`, this method will return a stream of flat bugreport.
302
+ *
303
+ * @param onProgress
304
+ * If `supportsBugReportZStream` is `false` and `supportsBugReportZProgress` is `true`,
305
+ * this callback will be called when progress is updated.
306
+ */
307
+ automatic(onProgress?: (completed: string, total: string) => void): {
308
+ type: "bugreport" | "bugreportz";
309
+ stream: ReadableStream<Uint8Array>;
310
+ } {
311
+ if (this.#supportsBugReportZStream) {
312
+ return { type: "bugreportz", stream: this.bugReportZStream() };
313
+ }
314
+
315
+ if (this.#supportsBugReportZ) {
316
+ let path: string | undefined;
317
+ let sync: AdbSync | undefined;
318
+ const controller = new AbortController();
319
+ const cleanup = async () => {
320
+ controller.abort();
321
+ await sync?.dispose();
322
+ if (path) {
323
+ await this.adb.rm(path);
324
+ }
325
+ };
326
+
327
+ return {
328
+ type: "bugreportz",
329
+ stream: new WrapReadableStream({
330
+ start: async () => {
331
+ path = await this.bugReportZ({
332
+ signal: controller.signal,
333
+ onProgress: this.#supportsBugReportZProgress
334
+ ? onProgress
335
+ : undefined,
336
+ });
337
+ sync = await this.adb.sync();
338
+ return sync.read(path);
339
+ },
340
+ cancel: cleanup,
341
+ close: cleanup,
342
+ }),
343
+ };
344
+ }
345
+
346
+ return { type: "bugreport", stream: this.bugReport() };
181
347
  }
182
348
  }
package/src/cmd.ts CHANGED
@@ -1,67 +1,101 @@
1
- import type { Adb, AdbSubprocessProtocolConstructor } from "@yume-chan/adb";
1
+ import type {
2
+ Adb,
3
+ AdbSubprocessProtocol,
4
+ AdbSubprocessProtocolConstructor,
5
+ AdbSubprocessWaitResult,
6
+ } from "@yume-chan/adb";
2
7
  import {
3
8
  AdbCommandBase,
4
9
  AdbFeature,
5
10
  AdbSubprocessNoneProtocol,
6
11
  AdbSubprocessShellProtocol,
7
12
  } from "@yume-chan/adb";
13
+ import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
8
14
 
9
15
  export class Cmd extends AdbCommandBase {
10
- private _supportsShellV2: boolean;
11
- private _supportsCmd: boolean;
12
- private _supportsAbb: boolean;
13
- private _supportsAbbExec: boolean;
14
-
15
- public get supportsShellV2() {
16
- return this._supportsShellV2;
16
+ #supportsShellV2: boolean;
17
+ get supportsShellV2() {
18
+ return this.#supportsShellV2;
17
19
  }
18
- public get supportsCmd() {
19
- return this._supportsCmd;
20
+
21
+ #supportsCmd: boolean;
22
+ get supportsCmd() {
23
+ return this.#supportsCmd;
20
24
  }
21
- public get supportsAbb() {
22
- return this._supportsAbb;
25
+
26
+ #supportsAbb: boolean;
27
+ get supportsAbb() {
28
+ return this.#supportsAbb;
23
29
  }
24
- public get supportsAbbExec() {
25
- return this._supportsAbbExec;
30
+
31
+ #supportsAbbExec: boolean;
32
+ get supportsAbbExec() {
33
+ return this.#supportsAbbExec;
26
34
  }
27
35
 
28
- public constructor(adb: Adb) {
36
+ constructor(adb: Adb) {
29
37
  super(adb);
30
- this._supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
31
- this._supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
32
- this._supportsAbb = adb.supportsFeature(AdbFeature.Abb);
33
- this._supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
38
+ this.#supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
39
+ this.#supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
40
+ this.#supportsAbb = adb.supportsFeature(AdbFeature.Abb);
41
+ this.#supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
34
42
  }
35
43
 
36
- public async spawn(
44
+ async spawn(
37
45
  shellProtocol: boolean,
38
46
  command: string,
39
47
  ...args: string[]
40
- ) {
48
+ ): Promise<AdbSubprocessProtocol> {
41
49
  let supportsAbb: boolean;
42
- let supportsCmd: boolean = this.supportsCmd;
50
+ let supportsCmd: boolean = this.#supportsCmd;
43
51
  let service: string;
44
52
  let Protocol: AdbSubprocessProtocolConstructor;
45
53
  if (shellProtocol) {
46
- supportsAbb = this._supportsAbb;
54
+ supportsAbb = this.#supportsAbb;
47
55
  supportsCmd &&= this.supportsShellV2;
48
56
  service = "abb";
49
57
  Protocol = AdbSubprocessShellProtocol;
50
58
  } else {
51
- supportsAbb = this._supportsAbbExec;
59
+ supportsAbb = this.#supportsAbbExec;
52
60
  service = "abb_exec";
53
61
  Protocol = AdbSubprocessNoneProtocol;
54
62
  }
55
63
 
56
64
  if (supportsAbb) {
57
- const socket = await this.adb.createSocket(
58
- `${service}:${command}\0${args.join("\0")}\0`
65
+ return new Protocol(
66
+ await this.adb.createSocket(
67
+ `${service}:${command}\0${args.join("\0")}\0`,
68
+ ),
59
69
  );
60
- return new Protocol(socket);
61
- } else if (supportsCmd) {
70
+ }
71
+
72
+ if (supportsCmd) {
62
73
  return Protocol.raw(this.adb, `cmd ${command} ${args.join(" ")}`);
63
- } else {
64
- throw new Error("Not supported");
65
74
  }
75
+
76
+ throw new Error("Not supported");
77
+ }
78
+
79
+ async spawnAndWait(
80
+ command: string,
81
+ ...args: string[]
82
+ ): Promise<AdbSubprocessWaitResult> {
83
+ const process = await this.spawn(true, command, ...args);
84
+
85
+ const [stdout, stderr, exitCode] = await Promise.all([
86
+ process.stdout
87
+ .pipeThrough(new DecodeUtf8Stream())
88
+ .pipeThrough(new ConcatStringStream()),
89
+ process.stderr
90
+ .pipeThrough(new DecodeUtf8Stream())
91
+ .pipeThrough(new ConcatStringStream()),
92
+ process.exit,
93
+ ]);
94
+
95
+ return {
96
+ stdout,
97
+ stderr,
98
+ exitCode,
99
+ };
66
100
  }
67
101
  }