From 7aa11b7a8abacf81dec20fff21216df35d333756 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 27 Jul 2025 18:50:33 -0700 Subject: Pulls in pengueno from npm --- u/process/argv.ts | 79 ------------------------------------ u/process/env.ts | 25 ------------ u/process/exec.ts | 86 ---------------------------------------- u/process/index.ts | 5 --- u/process/signals.ts | 49 ----------------------- u/process/validate_identifier.ts | 18 --------- 6 files changed, 262 deletions(-) delete mode 100644 u/process/argv.ts delete mode 100644 u/process/env.ts delete mode 100644 u/process/exec.ts delete mode 100644 u/process/index.ts delete mode 100644 u/process/signals.ts delete mode 100644 u/process/validate_identifier.ts (limited to 'u/process') diff --git a/u/process/argv.ts b/u/process/argv.ts deleted file mode 100644 index 396fa96..0000000 --- a/u/process/argv.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Either, type Mapper, type IEither, Optional } from '@emprespresso/pengueno'; - -export const isArgKey = (k: string): k is K => k.startsWith('--'); - -interface ArgHandler { - absent?: V; - unspecified?: V; - present: Mapper; -} - -export const getArg = ( - arg: K, - argv: Array, - whenValue: ArgHandler, -): IEither => { - const argIndex = Optional.from(argv.findIndex((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg)).filter( - (index) => index >= 0 && index < argv.length, - ); - if (!argIndex.present()) { - return Optional.from(whenValue.absent) - .map((v) => Either.right(v)) - .orSome(() => - Either.left( - new Error(`arg ${arg} is not present in arguments list and does not have an 'absent' value`), - ), - ) - .get(); - } - - return argIndex - .flatMap((idx) => - Optional.from(argv.at(idx)).map((_argv) => (_argv.includes('=') ? _argv.split('=')[1] : argv.at(idx + 1))), - ) - .filter((next) => !isArgKey(next)) - .map((next) => whenValue.present(next)) - .orSome(() => whenValue.unspecified) - .map((v) => Either.right(v)) - .get(); -}; - -type MappedArgs< - Args extends ReadonlyArray, - Handlers extends Partial>>, -> = { - [K in Args[number]]: K extends keyof Handlers ? (Handlers[K] extends ArgHandler ? T : string) : string; -}; - -export const argv = < - const Args extends ReadonlyArray, - const Handlers extends Partial>>, ->( - args: Args, - handlers?: Handlers, - argv = process.argv.slice(2), -): IEither> => { - type Result = MappedArgs; - - const defaultHandler: ArgHandler = { present: (value: string) => value }; - - const processArg = (arg: Args[number]): IEither => { - const handler = handlers?.[arg] ?? defaultHandler; - return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const); - }; - - const res = args - .map(processArg) - .reduce( - (acc: IEither>, current: IEither) => - acc.flatMap((accValue) => - current.mapRight(([key, value]) => ({ - ...accValue, - [key]: value, - })), - ), - Either.right(>{}), - ) - .mapRight((result) => result); - return res; -}; diff --git a/u/process/env.ts b/u/process/env.ts deleted file mode 100644 index f59fadf..0000000 --- a/u/process/env.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { IOptional, Either, Optional, type IEither, type ObjectFromList } from '@emprespresso/pengueno'; - -// type safe environment variables - -export const getEnv = (name: string): IOptional => Optional.from(process.env[name]); - -export const getRequiredEnv = (name: V): IEither => - Either.fromFailable(() => getEnv(name).get()).mapLeft( - () => new Error(`environment variable "${name}" is required D:`), - ); - -export const getRequiredEnvVars = (vars: Array): IEither> => { - type Environment = ObjectFromList; - const emptyEnvironment = Either.right({}); - const addTo = (env: Environment, key: V, val: string) => - { - ...env, - [key]: val, - }; - return vars.reduce( - (environment, key) => - environment.joinRight(getRequiredEnv(key), (value, environment) => addTo(environment, key, value)), - emptyEnvironment, - ); -}; diff --git a/u/process/exec.ts b/u/process/exec.ts deleted file mode 100644 index 46b31c4..0000000 --- a/u/process/exec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - Either, - IEither, - type ITraceable, - LogLevel, - LogMetricTraceSupplier, - Metric, - TraceUtil, -} from '@emprespresso/pengueno'; -import { exec } from 'node:child_process'; - -export type Command = string[] | string; -export type StdStreams = { stdout: string; stderr: string }; - -export const CmdMetric = Metric.fromName('Exec').asResult(); -type Environment = Record; -type Options = { streamTraceable?: Array<'stdout' | 'stderr'>; env?: Environment; clearEnv?: boolean }; -export const getStdout = ( - cmd: ITraceable, - options: Options = { streamTraceable: [] }, -): Promise> => - cmd - .flatMap(TraceUtil.withFunctionTrace(getStdout)) - .flatMap((tCmd) => tCmd.traceScope(() => `Command = ${tCmd.get()}`)) - .map((tCmd) => { - const cmd = tCmd.get(); - const _exec = typeof cmd === 'string' ? cmd : cmd.join(' '); - const env = options.clearEnv ? options.env : { ...process.env, ...options.env }; - return Either.fromFailableAsync( - new Promise((res, rej) => { - const proc = exec(_exec, { env }); - let stdout = ''; - proc.stdout?.on('data', (d) => { - const s = d.toString(); - stdout += s; - if (options.streamTraceable?.includes('stdout')) { - tCmd.trace.trace(s); - } - }); - let stderr = ''; - proc.stderr?.on('data', (d) => { - const s = d.toString(); - stdout += s; - if (options.streamTraceable?.includes('stderr')) { - tCmd.trace.trace(s); - } - }); - - proc.on('exit', (code) => { - const streams = { stdout, stderr }; - if (code === 0) { - res(streams); - } else { - rej(new Error(`exited with non-zero code: ${code}. ${stderr}`)); - } - }); - }), - ); - }) - .map( - TraceUtil.promiseify((tEitherStdStreams) => - tEitherStdStreams.get().mapRight(({ stderr, stdout }) => { - if (stderr) tEitherStdStreams.trace.traceScope(LogLevel.DEBUG).trace(`StdErr = ${stderr}`); - return stdout; - }), - ), - ) - .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(CmdMetric))) - .get(); - -export const getStdoutMany = ( - cmds: ITraceable, LogMetricTraceSupplier>, - options: Options = { streamTraceable: [] }, -): Promise>> => - cmds - .coExtend((t) => t.get()) - .reduce( - async (_result, tCmd) => { - const result = await _result; - return result.joinRightAsync( - () => tCmd.map((cmd) => getStdout(cmd, options)).get(), - (stdout, pre) => pre.concat(stdout), - ); - }, - Promise.resolve(Either.right>([])), - ); diff --git a/u/process/index.ts b/u/process/index.ts deleted file mode 100644 index 2d74a5f..0000000 --- a/u/process/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './exec.js'; -export * from './env.js'; -export * from './validate_identifier.js'; -export * from './argv.js'; -export * from './signals.js'; diff --git a/u/process/signals.ts b/u/process/signals.ts deleted file mode 100644 index c4feb7a..0000000 --- a/u/process/signals.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - Either, - IEither, - IMetric, - ITraceable, - LogMetricTrace, - LogMetricTraceSupplier, - Mapper, - Metric, - Optional, - ResultMetric, - SideEffect, - TraceUtil, -} from '@emprespresso/pengueno'; - -export const SigIntMetric = Metric.fromName('SigInt').asResult(); -export const SigTermMetric = Metric.fromName('SigTerm').asResult(); - -export interface Closeable { - readonly close: SideEffect>; -} - -export class Signals { - public static async awaitClose( - t: ITraceable, LogMetricTraceSupplier>, - ): Promise> { - const success: IEither = Either.right(undefined); - return new Promise>((res) => { - const metricizedInterruptHandler = (metric: ResultMetric) => (err: Error | undefined) => - t - .flatMap(TraceUtil.withMetricTrace(metric)) - .peek((_t) => _t.trace.trace('closing')) - .move( - Optional.from(err) - .map((e) => Either.left(e)) - .orSome(() => success) - .get(), - ) - .flatMap(TraceUtil.traceResultingEither(metric)) - .map((e) => res(e.get())) - .peek((_t) => _t.trace.trace('finished')) - .get(); - const sigintCloser = metricizedInterruptHandler(SigIntMetric); - const sigtermCloser = metricizedInterruptHandler(SigTermMetric); - process.on('SIGINT', () => t.flatMap(TraceUtil.withTrace('SIGINT')).get().close(sigintCloser)); - process.on('SIGTERM', () => t.flatMap(TraceUtil.withTrace('SIGTERM')).get().close(sigtermCloser)); - }); - } -} diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts deleted file mode 100644 index 1ff3791..0000000 --- a/u/process/validate_identifier.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Either, type IEither } from '@emprespresso/pengueno'; - -export const validateIdentifier = (token: string) => { - return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes('..'); -}; - -// ensure {@param obj} is a Record with stuff that won't -// have the potential for shell injection, just to be super safe. -type InvalidEntry = [K, T]; -export const validateExecutionEntries = ( - obj: Record, -): IEither>, Record> => { - const invalidEntries = >>( - Object.entries(obj).filter((e) => !e.every((x) => typeof x === 'string' && validateIdentifier(x))) - ); - if (invalidEntries.length > 0) return Either.left(invalidEntries); - return Either.right(>obj); -}; -- cgit v1.2.3-70-g09d2