From ffd49e82ee77a981ebf711b1e1bc9967e59d9841 Mon Sep 17 00:00:00 2001 From: Thomas Scholtes Date: Mon, 27 May 2024 09:29:10 +0200 Subject: [PATCH] Update execa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- package-lock.json | 227 ++++++++++++++++++++++++++++++----- package.json | 2 +- tests/support/fixtures.ts | 6 +- tests/support/globalSetup.ts | 2 - tests/support/peerManager.ts | 74 +++++++----- tests/support/process.ts | 84 ------------- tests/support/project.ts | 3 +- 7 files changed, 248 insertions(+), 150 deletions(-) delete mode 100644 tests/support/process.ts diff --git a/package-lock.json b/package-lock.json index f6a221acd..458e0f440 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 66d43e6a0..cde7730db 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tests/support/fixtures.ts b/tests/support/fixtures.ts index c44d348ab..8a3d6fb6c 100644 --- a/tests/support/fixtures.ts +++ b/tests/support/fixtures.ts @@ -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", diff --git a/tests/support/globalSetup.ts b/tests/support/globalSetup.ts index 7ffeaef50..554d296d1 100644 --- a/tests/support/globalSetup.ts +++ b/tests/support/globalSetup.ts @@ -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(); }; } diff --git a/tests/support/peerManager.ts b/tests/support/peerManager.ts index 4647b3645..34b5ea386 100644 --- a/tests/support/peerManager.ts +++ b/tests/support/peerManager.ts @@ -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 {} -// 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 ` `. - 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; + stderr: (line: unknown) => AsyncGenerator; + 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; #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; } diff --git a/tests/support/process.ts b/tests/support/process.ts deleted file mode 100644 index dced804a3..000000000 --- a/tests/support/process.ts +++ /dev/null @@ -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(); - } -} diff --git a/tests/support/project.ts b/tests/support/project.ts index 214a2f4ac..08d1ad870 100644 --- a/tests/support/project.ts +++ b/tests/support/project.ts @@ -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) { +export function extractPatchId(cmdOutput: { stderr: string }) { const match = cmdOutput.stderr.match(/[0-9a-f]{40}/); if (match) { return match[0];