From bc80cc974a336a0f3c19148a929e7c1fc4169e33 Mon Sep 17 00:00:00 2001 From: BluJ Date: Fri, 9 Jun 2023 10:58:29 -0600 Subject: [PATCH] feat: Effects that shouldn't be needed removed from utils --- lib/health/checkFns/checkPortListening.ts | 12 +++- lib/health/checkFns/runHealthScript.ts | 3 +- lib/mainFn/Daemons.ts | 2 +- lib/test/utils.splitCommand.test.ts | 38 +++++++++++ lib/util/getNetworkInterface.ts | 1 - lib/util/splitCommand.ts | 17 +++++ lib/util/stringFromStdErrOut.ts | 6 ++ lib/util/utils.ts | 78 ++++++++++------------- 8 files changed, 108 insertions(+), 49 deletions(-) create mode 100644 lib/test/utils.splitCommand.test.ts create mode 100644 lib/util/splitCommand.ts create mode 100644 lib/util/stringFromStdErrOut.ts diff --git a/lib/health/checkFns/checkPortListening.ts b/lib/health/checkFns/checkPortListening.ts index c4772cf..0714407 100644 --- a/lib/health/checkFns/checkPortListening.ts +++ b/lib/health/checkFns/checkPortListening.ts @@ -1,5 +1,6 @@ import { Effects } from "../../types" import { createUtils } from "../../util" +import { stringFromStdErrOut } from "../../util/stringFromStdErrOut" import { CheckResult } from "./CheckResult" export function containsAddress(x: string, port: number) { const readPorts = x @@ -32,10 +33,17 @@ export async function checkPortListening( Promise.resolve().then(async () => { const hasAddress = containsAddress( - await utils.runCommand(`cat /proc/net/tcp`, {}), + await utils.childProcess + .exec(`cat /proc/net/tcp`, {}) + .then(stringFromStdErrOut), port, ) || - containsAddress(await utils.runCommand("cat /proc/net/udp", {}), port) + containsAddress( + await utils.childProcess + .exec("cat /proc/net/udp", {}) + .then(stringFromStdErrOut), + port, + ) if (hasAddress) { return { status: "passing", message: options.successMessage } } diff --git a/lib/health/checkFns/runHealthScript.ts b/lib/health/checkFns/runHealthScript.ts index bafa549..4bc4556 100644 --- a/lib/health/checkFns/runHealthScript.ts +++ b/lib/health/checkFns/runHealthScript.ts @@ -1,5 +1,6 @@ import { CommandType, Effects } from "../../types" import { createUtils } from "../../util" +import { stringFromStdErrOut } from "../../util/stringFromStdErrOut" import { CheckResult } from "./CheckResult" import { timeoutPromise } from "./index" @@ -22,7 +23,7 @@ export const runHealthScript = async ( ): Promise => { const utils = createUtils(effects) const res = await Promise.race([ - utils.runCommand(runCommand, { timeout }), + utils.childProcess.exec(runCommand, { timeout }).then(stringFromStdErrOut), timeoutPromise(timeout), ]).catch((e) => { console.warn(errorMessage) diff --git a/lib/mainFn/Daemons.ts b/lib/mainFn/Daemons.ts index 0dfa95d..cbc9fd3 100644 --- a/lib/mainFn/Daemons.ts +++ b/lib/mainFn/Daemons.ts @@ -7,7 +7,7 @@ import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types" import { createUtils } from "../util" type Daemon = { id: "" extends Id ? never : Id - command: string + command: ValidIfNoStupidEscape | [string, ...string[]] env?: Record ready: { display: string | null diff --git a/lib/test/utils.splitCommand.test.ts b/lib/test/utils.splitCommand.test.ts new file mode 100644 index 0000000..08d6a8e --- /dev/null +++ b/lib/test/utils.splitCommand.test.ts @@ -0,0 +1,38 @@ +import { getHostname } from "../util/getNetworkInterface" +import { splitCommand } from "../util/splitCommand" + +describe("splitCommand ", () => { + const inputToExpected = [ + ["cat", ["cat"]], + [["cat"], ["cat"]], + [ + ["cat", "hello all my homies"], + ["cat", "hello all my homies"], + ], + ["cat hello world", ["cat", "hello", "world"]], + ["cat hello 'big world'", ["cat", "hello", "big world"]], + [`cat hello "big world"`, ["cat", "hello", "big world"]], + // Too many spaces + ["cat ", ["cat"]], + [["cat "], ["cat "]], + [ + ["cat ", "hello all my homies "], + ["cat ", "hello all my homies "], + ], + ["cat hello world ", ["cat", "hello", "world"]], + [ + " cat hello 'big world' ", + ["cat", "hello", "big world"], + ], + [ + ` cat hello "big world" `, + ["cat", "hello", "big world"], + ], + ] + + for (const [input, expectValue] of inputToExpected) { + test(`should return ${expectValue} for ${input}`, () => { + expect(splitCommand(input as any)).toEqual(expectValue) + }) + } +}) diff --git a/lib/util/getNetworkInterface.ts b/lib/util/getNetworkInterface.ts index 51e83fb..e76b7f3 100644 --- a/lib/util/getNetworkInterface.ts +++ b/lib/util/getNetworkInterface.ts @@ -10,7 +10,6 @@ export const getHostname = (url: string): HostName | null => { if (!founds) return null const parts = founds.split("@") const last = parts[parts.length - 1] as HostName | null - console.log({ url, parts, founds, last }) return last } diff --git a/lib/util/splitCommand.ts b/lib/util/splitCommand.ts new file mode 100644 index 0000000..bf55a74 --- /dev/null +++ b/lib/util/splitCommand.ts @@ -0,0 +1,17 @@ +import { arrayOf, string } from "ts-matches" +import { ValidIfNoStupidEscape } from "../types" + +export const splitCommand = ( + command: ValidIfNoStupidEscape | [string, ...string[]], +): string[] => { + if (arrayOf(string).test(command)) return command + return String(command) + .split('"') + .flatMap((x, i) => + i % 2 !== 0 + ? [x] + : x.split("'").flatMap((x, i) => (i % 2 !== 0 ? [x] : x.split(" "))), + ) + .map((x) => x.trim()) + .filter(Boolean) +} diff --git a/lib/util/stringFromStdErrOut.ts b/lib/util/stringFromStdErrOut.ts new file mode 100644 index 0000000..452aaa0 --- /dev/null +++ b/lib/util/stringFromStdErrOut.ts @@ -0,0 +1,6 @@ +export async function stringFromStdErrOut(x: { + stdout: string + stderr: string +}) { + return x?.stderr ? Promise.reject(x.stderr) : x.stdout +} diff --git a/lib/util/utils.ts b/lib/util/utils.ts index 4890063..d8b5e64 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -1,4 +1,3 @@ -import FileHelper from "./fileHelper" import nullIfEmpty from "./nullIfEmpty" import { CheckResult, @@ -12,6 +11,7 @@ import { ExtractStore, InterfaceId, PackageId, + ValidIfNoStupidEscape, } from "../types" import { GetSystemSmtp } from "./GetSystemSmtp" import { DefaultString } from "../config/configTypes" @@ -34,8 +34,14 @@ import { GetNetworkInterfaces, getNetworkInterfaces, } from "./getNetworkInterfaces" +import * as CP from "node:child_process" +import { promisify } from "node:util" +import { splitCommand } from "./splitCommand" -import { exec } from "node:child_process" +const childProcess = { + exec: promisify(CP.exec), + execFile: promisify(CP.execFile), +} export type Utils = { checkPortListening( @@ -55,6 +61,7 @@ export type Utils = { errorMessage?: string }, ): Promise + childProcess: typeof childProcess createInterface: (options: { name: string id: string @@ -93,15 +100,10 @@ export type Utils = { }) => GetNetworkInterfaces & WrapperOverWrite } nullIfEmpty: typeof nullIfEmpty - readFile: (fileHelper: FileHelper) => ReturnType["read"]> - runDaemon: ( - command: string, + runDaemon: ( + command: ValidIfNoStupidEscape | [string, ...string[]], options: { env?: Record }, ) => Promise - runCommand: ( - command: string, - options: { env?: Record; timeout?: number }, - ) => Promise store: { get: ( packageId: string, @@ -115,10 +117,6 @@ export type Utils = { value: ExtractStore, ) => Promise } - writeFile: ( - fileHelper: FileHelper, - data: A, - ) => ReturnType["write"]> } export const utils = ( effects: Effects, @@ -135,6 +133,7 @@ export const utils = ( path: string search: Record }) => new NetworkInterfaceBuilder({ ...options, effects }), + childProcess, getSystemSmtp: () => new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite, @@ -143,9 +142,6 @@ export const utils = ( single: (id: string) => new SingleHost({ id, effects }), multi: (id: string) => new MultiHost({ id, effects }), }, - readFile: (fileHelper: FileHelper) => fileHelper.read(effects), - writeFile: (fileHelper: FileHelper, data: A) => - fileHelper.write(data, effects), nullIfEmpty, networkInterface: { @@ -178,45 +174,39 @@ export const utils = ( ) => effects.store.set({ value, path: path as any }), }, - runDaemon: async ( - command: string, + runDaemon: async ( + command: ValidIfNoStupidEscape | [string, ...string[]], options: { env?: Record }, ): Promise => { - let childProcess: null | { - kill(signal?: number | string): void - } = null - const answer = new Promise( - (resolve, reject) => - (childProcess = exec(command, options, (error, stdout, stderr) => { - if (error) return reject(error.toString()) - if (stderr) return reject(stderr.toString()) - return resolve(stdout.toString()) - })), - ) + const commands = splitCommand(command) + const childProcess = CP.spawn(commands[0], commands.slice(1), options) + const answer = new Promise((resolve, reject) => { + const output: string[] = [] + childProcess.stdout.on("data", (data) => { + output.push(data.toString()) + }) + const outputError: string[] = [] + childProcess.stderr.on("data", (data) => { + outputError.push(data.toString()) + }) + + childProcess.on("close", (code) => { + if (code === 0) { + return resolve(output.join("")) + } + return reject(outputError.join("")) + }) + }) return { wait() { return answer }, async term() { - childProcess?.kill() + childProcess.kill() }, } }, - runCommand: async ( - command: string, - options: { env?: Record; timeout?: number }, - ): Promise => { - const answer = new Promise((resolve, reject) => - exec(command, options, (error, stdout, stderr) => { - if (error) return reject(error.toString()) - if (stderr) return reject(stderr.toString()) - return resolve(stdout.toString()) - }), - ) - - return answer - }, checkPortListening: checkPortListening.bind(null, effects), checkWebUrl: checkWebUrl.bind(null, effects),