Update execa

The execa update allows us two simplify two things

1. Output collection in a log file with a prefix can be done through
   `stdout` and `stderr` options of `execa`.
2. We don’t need to ensure that all subprocesses are killed when the
   main process exits. `execa` takes care of this
This commit is contained in:
Thomas Scholtes
2024-05-27 09:29:10 +02:00
parent 04de7e7526
commit ffd49e82ee
7 changed files with 248 additions and 150 deletions
+199 -28
View File
@@ -49,7 +49,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-svelte": "^2.35.1",
"execa": "^8.0.1",
"execa": "^9.0.0",
"get-port": "^7.1.0",
"happy-dom": "^14.3.8",
"prettier": "^3.2.5",
@@ -908,6 +908,12 @@
"win32"
]
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
"integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
"dev": true
},
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -935,6 +941,18 @@
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
"dev": true
},
"node_modules/@sindresorhus/merge-streams": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
"integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
@@ -2430,23 +2448,26 @@
}
},
"node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-9.1.0.tgz",
"integrity": "sha512-lSgHc4Elo2m6bUDhc3Hl/VxvUDJdQWI40RZ4KMY9bKRc+hgMOT7II/JjbNDhI8VnMtrCb7U/fhpJIkLORZozWw==",
"dev": true,
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
"cross-spawn": "^7.0.3",
"get-stream": "^8.0.1",
"human-signals": "^5.0.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"figures": "^6.1.0",
"get-stream": "^9.0.0",
"human-signals": "^7.0.0",
"is-plain-obj": "^4.1.0",
"is-stream": "^4.0.1",
"npm-run-path": "^5.2.0",
"pretty-ms": "^9.0.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^3.0.0"
"strip-final-newline": "^4.0.0",
"yoctocolors": "^2.0.0"
},
"engines": {
"node": ">=16.17"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
@@ -2518,6 +2539,21 @@
"reusify": "^1.0.4"
}
},
"node_modules/figures": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
"integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
"dev": true,
"dependencies": {
"is-unicode-supported": "^2.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -2675,12 +2711,16 @@
}
},
"node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
"integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"dev": true,
"dependencies": {
"@sec-ant/readable-stream": "^0.4.1",
"is-stream": "^4.0.1"
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2957,12 +2997,12 @@
}
},
"node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-7.0.0.tgz",
"integrity": "sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==",
"dev": true,
"engines": {
"node": ">=16.17.0"
"node": ">=18.18.0"
}
},
"node_modules/ieee754": {
@@ -3107,6 +3147,18 @@
"node": ">=8"
}
},
"node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
@@ -3116,12 +3168,24 @@
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-unicode-supported": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
"integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -3810,6 +3874,18 @@
"node": ">=6"
}
},
"node_modules/parse-ms": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
"integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@@ -4115,6 +4191,21 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/pretty-ms": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.0.0.tgz",
"integrity": "sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==",
"dev": true,
"dependencies": {
"parse-ms": "^4.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/property-information": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz",
@@ -4494,12 +4585,12 @@
}
},
"node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
"integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
"dev": true,
"engines": {
"node": ">=12"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5153,6 +5244,74 @@
}
}
},
"node_modules/vitest/node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^8.0.1",
"human-signals": "^5.0.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^3.0.0"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/vitest/node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"dev": true,
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vitest/node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"dev": true,
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/vitest/node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vitest/node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vscode-oniguruma": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz",
@@ -5273,6 +5432,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yoctocolors": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.0.2.tgz",
"integrity": "sha512-Ct97huExsu7cWeEjmrXlofevF8CvzUglJ4iGUet5B8xn1oumtAZBpHU4GzYuoE6PVqcZ5hghtBrSlhwHuR1Jmw==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
+1 -1
View File
@@ -36,7 +36,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-svelte": "^2.35.1",
"execa": "^8.0.1",
"execa": "^9.0.0",
"get-port": "^7.1.0",
"happy-dom": "^14.3.8",
"prettier": "^3.2.5",
+3 -3
View File
@@ -6,8 +6,8 @@ import * as Path from "node:path";
import assert from "node:assert";
import { fileURLToPath } from "node:url";
import { test as base, expect } from "@playwright/test";
import { execa } from "execa";
import * as Process from "./process.js";
import * as issue from "@tests/support/cobs/issue.js";
import * as logLabel from "@tests/support/logPrefix.js";
import * as patch from "@tests/support/cobs/patch.js";
@@ -190,7 +190,7 @@ export async function createSourceBrowsingFixture(
const projectName = "source-browsing";
const sourceBrowsingDir = Path.join(tmpDir, "repos", projectName);
await Fs.mkdir(sourceBrowsingDir, { recursive: true });
await Process.spawn("tar", [
await execa("tar", [
"-xf",
Path.join(fixturesDir, `repos/${projectName}.tar.bz2`),
"-C",
@@ -568,7 +568,7 @@ export async function createCobsFixture(peer: RadiclePeer) {
export async function createMarkdownFixture(peer: RadiclePeer) {
await Fs.mkdir(Path.join(tmpDir, "repos", "markdown"));
await Process.spawn("tar", [
await execa("tar", [
"-xf",
Path.join(fixturesDir, "repos", "markdown.tar.bz2"),
"-C",
-2
View File
@@ -13,7 +13,6 @@ import {
gitOptions,
} from "@tests/support/fixtures.js";
import { createPeerManager } from "@tests/support/peerManager.js";
import { killAllProcesses } from "@tests/support/process.js";
export default async function globalSetup(): Promise<() => void> {
try {
@@ -78,6 +77,5 @@ export default async function globalSetup(): Promise<() => void> {
return async () => {
await peerManager.shutdown();
killAllProcesses();
};
}
+44 -30
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { BaseUrl } from "@httpd-client";
import type { ExecaChildProcess, Options as ExecaOptions } from "execa";
import type * as Execa from "execa";
import { execa } from "execa";
import * as Fs from "node:fs/promises";
import * as Os from "node:os";
import * as Path from "node:path";
@@ -10,10 +10,7 @@ import * as Util from "node:util";
import getPort from "get-port";
import matches from "lodash/matches.js";
import waitOn from "wait-on";
import { execa } from "execa";
import * as readline from "node:readline/promises";
import * as Process from "./process.js";
import { randomTag } from "@tests/support/support.js";
import { sleep } from "@app/lib/sleep.js";
import { array, literal, number, object, string, union, z } from "zod";
@@ -151,13 +148,20 @@ export const NodeConfigSchema = object({
export interface NodeConfig extends z.infer<typeof NodeConfigSchema> {}
// Options passed to `RadiclePeer#rad` and `RadiclePeer#git` that control how
// the process is spawned.
interface SpawnOptions extends ExecaOptions {
// Sets the prefix for the commands output in the logs. If `null`, does not
// log any output. Defaults to `<peer name> <binary name>`.
logPrefix?: string | null;
}
// Specialize the return type of `execa()` to guarantee that `stdout` and
// `stderr` are strings.
type SpawnResult = Execa.ResultPromise<
SpawnOptions & {
stdout: (line: unknown) => AsyncGenerator<string, void, void>;
stderr: (line: unknown) => AsyncGenerator<string, void, void>;
encoding: "utf8";
}
>;
type SpawnOptions = Omit<
Execa.Options,
"stdin" | "stdout" | "stderr" | "lines" | "encoding"
>;
export class RadiclePeer {
public checkoutPath: string;
@@ -171,10 +175,10 @@ export class RadiclePeer {
#gitOptions?: Record<string, string>;
#listenSocketAddr?: string;
#httpdBaseUrl?: BaseUrl;
#nodeProcess?: ExecaChildProcess;
#nodeProcess?: SpawnResult;
// Name for easy identification. Used on file system and in logs.
#name: string;
#childProcesses: ExecaChildProcess[] = [];
#childProcesses: SpawnResult[] = [];
private constructor(props: {
checkoutPath: string;
@@ -296,7 +300,6 @@ export class RadiclePeer {
const { stdout } = this.rad(["node", "events"], {
cwd: this.#radHome,
logPrefix: null,
});
if (!stdout) {
@@ -327,6 +330,8 @@ export class RadiclePeer {
}
public async stopNode() {
// Don’t leak unhandled rejections when forcefully killing the process
this.#nodeProcess?.catch(() => {});
this.#nodeProcess?.kill("SIGTERM");
await waitOn({
@@ -343,7 +348,11 @@ export class RadiclePeer {
public async shutdown() {
// We don’t care about proper cleanup. We just want to make sure that no
// processes are running anymore.
this.#childProcesses.forEach(p => p.kill("SIGKILL"));
this.#childProcesses.forEach(p => {
// Don’t leak unhandled rejections when forcefully killing the process
p.catch(() => {});
p.kill("SIGKILL");
});
}
public get address(): string {
@@ -373,11 +382,11 @@ export class RadiclePeer {
return this.#httpdBaseUrl;
}
public git(args: string[] = [], opts?: SpawnOptions): ExecaChildProcess {
public git(args: string[] = [], opts?: SpawnOptions): SpawnResult {
return this.spawn("git", args, { ...opts });
}
public rad(args: string[] = [], opts?: SpawnOptions): ExecaChildProcess {
public rad(args: string[] = [], opts?: SpawnOptions): SpawnResult {
return this.spawn("rad", args, { ...opts });
}
@@ -385,8 +394,18 @@ export class RadiclePeer {
cmd: string,
args: string[] = [],
opts?: SpawnOptions,
): ExecaChildProcess {
opts = {
): SpawnResult {
const prefix = logPrefix(`${this.#name} ${cmd}`);
const outputLog = this.#outputLog;
function* logWithPrefix(line: unknown) {
if (typeof line === "string") {
outputLog.write(`${prefix} ${line}\n`, "utf8");
}
yield line;
}
const childProcess = execa(cmd, args, {
...opts,
env: {
GIT_CONFIG_GLOBAL: "/dev/null",
@@ -399,17 +418,12 @@ export class RadiclePeer {
...opts?.env,
...this.#gitOptions,
},
};
const childProcess = Process.spawn(cmd, args, opts);
this.#childProcesses.push(childProcess);
encoding: "utf8",
stdout: logWithPrefix,
stderr: logWithPrefix,
});
if (opts.logPrefix !== null) {
void Process.prefixOutput(
childProcess,
opts.logPrefix || `${this.#name} ${cmd}`,
this.#outputLog,
);
}
this.#childProcesses.push(childProcess);
return childProcess;
}
-84
View File
@@ -1,84 +0,0 @@
import type { ExecaChildProcess, Options } from "execa";
import * as Stream from "node:stream";
import { StringDecoder } from "string_decoder";
import { execa } from "execa";
import { logPrefix } from "./logPrefix.js";
// Processes that should be SIGKILLed when the Node process shutsdown.
// We add all proxy and node instances that we spawn to this list.
const processes: ExecaChildProcess[] = [];
process.on("exit", killAllProcesses);
process.on("SIGINT", killAllProcesses);
process.on("SIGTERM", killAllProcesses);
// Kill all processes with SIGKILL
export function killAllProcesses(): void {
for (const process of processes) {
if (process.exitCode === null) {
process.kill("SIGKILL");
}
}
}
// Spawn a process with `execa` and register it.
//
// The process will be killed by `killAllProcesses`.
export function spawn(
bin: string,
args: string[],
options?: Options,
): ExecaChildProcess {
const child = execa(bin, args, options);
processes.push(child);
return child;
}
// Forwards piped `stdout` and `stderr` of a child process to `output`
// and prefixes it with the given label. The prefix is colored.
export function prefixOutput(
childProcess: ExecaChildProcess,
label: string,
output: Stream.Writable,
): ExecaChildProcess {
const pref = logPrefix(label);
if (childProcess.stdout) {
const stdoutPrefix = new LinePrefix(pref);
childProcess.stdout.pipe(stdoutPrefix).pipe(output, { end: false });
}
if (childProcess.stderr) {
const stderrPrefix = new LinePrefix(pref);
childProcess.stderr.pipe(stderrPrefix).pipe(output, { end: false });
}
return childProcess;
}
// A transform that prefixes each line from the source with the given
// string and pushes it to the sink.
class LinePrefix extends Stream.Transform {
private buffer: string = "";
private stringDecoder = new StringDecoder();
public constructor(private prefix: string) {
super();
}
public _transform(data: Buffer, _encoding: string, next: () => void): void {
const str = this.buffer + this.stringDecoder.write(data);
const lines = str.split(/\r?\n/);
this.buffer = lines.pop() || "";
lines.forEach(line => this.push(`${this.prefix}${line}\n`));
next();
}
public _flush(done: () => void): void {
const rest = `${this.buffer}${this.stringDecoder.end()}`;
if (rest) {
this.push(`${this.prefix}${rest}\n`);
}
done();
}
}
+1 -2
View File
@@ -1,6 +1,5 @@
import type { Locator, Page } from "@playwright/test";
import type { RadiclePeer } from "@tests/support/peerManager";
import type { ExecaReturnValue } from "execa";
import * as Path from "node:path";
import { expect } from "@playwright/test";
@@ -52,7 +51,7 @@ export async function createProject(
return { rid, projectFolder, defaultBranch };
}
export function extractPatchId(cmdOutput: ExecaReturnValue<string>) {
export function extractPatchId(cmdOutput: { stderr: string }) {
const match = cmdOutput.stderr.match(/[0-9a-f]{40}/);
if (match) {
return match[0];