From 58be1809c46cbe517a18d86d0af52179dcc5cbf6 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 29 Jun 2025 17:31:30 -0700 Subject: Move to nodejs and also lots of significant refactoring that should've been broken up but idgaf --- u/process/argv.ts | 42 ++++++++++++++++++++++++------------------ u/process/env.ts | 13 +++++++------ u/process/index.ts | 1 + u/process/run.ts | 36 +++++++++++++++--------------------- u/process/signals.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 u/process/signals.ts (limited to 'u/process') diff --git a/u/process/argv.ts b/u/process/argv.ts index dcdba85..dca5098 100644 --- a/u/process/argv.ts +++ b/u/process/argv.ts @@ -1,4 +1,4 @@ -import { Either, type Mapper, type IEither } from '@emprespresso/pengueno'; +import { Either, type Mapper, type IEither, Optional } from '@emprespresso/pengueno'; export const isArgKey = (k: string): k is K => k.startsWith('--'); @@ -13,22 +13,27 @@ export const getArg = ( argv: Array, whenValue: ArgHandler, ): IEither => { - const value = - argv - .filter((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg) - .map((_argv, i) => { - const next = _argv.includes('=') ? _argv.split('=')[1] : argv.at(i + 1); - if (next) { - if (isArgKey(next)) return whenValue.unspecified; - return whenValue.present(next); - } - return whenValue.unspecified; - }) - .find((x) => x) ?? whenValue.absent; - if (value === undefined) { - return Either.left(new Error('no value specified for ' + arg)); + 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 Either.right(value); + + return argIndex + .flatMap((idx) => + Optional.from(argv.at(idx)).map((_argv) => (_argv.includes('=') ? _argv.split('=')[1] : argv.at(idx + 1))), + ) + .map((next) => (isArgKey(next) ? whenValue.unspecified : whenValue.present(next))) + .map((v) => Either.right(v)) + .get(); }; type MappedArgs< @@ -55,10 +60,10 @@ export const argv = < return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const); }; - return args + const res = args .map(processArg) .reduce( - (acc: IEither>, current: IEither) => + (acc: IEither>, current: IEither) => acc.flatMap((accValue) => current.mapRight(([key, value]) => ({ ...accValue, @@ -68,4 +73,5 @@ export const argv = < Either.right(>{}), ) .mapRight((result) => result); + return res; }; diff --git a/u/process/env.ts b/u/process/env.ts index 1e4fd32..9a55488 100644 --- a/u/process/env.ts +++ b/u/process/env.ts @@ -1,10 +1,11 @@ -import { Either, type IEither } from '@emprespresso/pengueno'; +import { IOptional, Either, Optional, type IEither } from '@emprespresso/pengueno'; -export const getRequiredEnv = (name: V): IEither => - Either.fromFailable(() => process.env[name] as V | undefined) // could throw when no permission. - .flatMap( - (v) => (v && Either.right(v)) || Either.left(new Error(`environment variable "${name}" is required D:`)), - ); +export const getEnv = (name: string): IOptional => Optional.from(process.env[name]); + +export const getRequiredEnv = (name: string): IEither => + Either.fromFailable(() => getEnv(name).get()).mapLeft( + () => new Error(`environment variable "${name}" is required D:`), + ); type ObjectFromList, V = string> = { [K in T extends ReadonlyArray ? U : never]: V; diff --git a/u/process/index.ts b/u/process/index.ts index 4ffbf2a..6945a0f 100644 --- a/u/process/index.ts +++ b/u/process/index.ts @@ -2,3 +2,4 @@ export * from './env.js'; export * from './run.js'; export * from './validate_identifier.js'; export * from './argv.js'; +export * from './signals.js'; diff --git a/u/process/run.ts b/u/process/run.ts index e3c4c3d..1d19129 100644 --- a/u/process/run.ts +++ b/u/process/run.ts @@ -1,9 +1,10 @@ import { Either, - type IEither, + IEither, type ITraceable, LogLevel, - type LogTraceSupplier, + LogMetricTraceSupplier, + Metric, TraceUtil, } from '@emprespresso/pengueno'; import { promisify } from 'node:util'; @@ -13,34 +14,27 @@ const exec = promisify(execCallback); export type Command = string[] | string; export type StdStreams = { stdout: string; stderr: string }; +export const CmdMetric = Metric.fromName('Exec').asResult(); export const getStdout = ( - c: ITraceable, + c: ITraceable, options: { env?: Record; clearEnv?: boolean } = {}, ): Promise> => c - .bimap(TraceUtil.withFunctionTrace(getStdout)) - .bimap((tCmd) => { + .flatMap(TraceUtil.withFunctionTrace(getStdout)) + .flatMap((tCmd) => tCmd.traceScope(() => `Command = ${tCmd.get()}`)) + .map((tCmd) => { const cmd = tCmd.get(); - tCmd.trace.trace(`Command = ${cmd} :> im gonna run this command! `); - const _exec = typeof cmd === 'string' ? cmd : cmd.join(' '); const env = options.clearEnv ? options.env : { ...process.env, ...options.env }; - - const p: Promise> = Either.fromFailableAsync(exec(_exec, { env })); - return [p, `Command = ${_exec}`]; + return Either.fromFailableAsync(exec(_exec, { env })); }) .map( - TraceUtil.promiseify( - (tEitherProcess): IEither => - tEitherProcess.get().fold(({ isLeft, value }) => { - if (isLeft) { - return Either.left(value); - } - if (value.stderr) { - tEitherProcess.trace.addTrace(LogLevel.DEBUG).trace(`StdErr = ${value.stderr}`); - } - return Either.right(value.stdout); - }), + 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(); diff --git a/u/process/signals.ts b/u/process/signals.ts new file mode 100644 index 0000000..c4feb7a --- /dev/null +++ b/u/process/signals.ts @@ -0,0 +1,49 @@ +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)); + }); + } +} -- cgit v1.2.3-70-g09d2