diff options
Diffstat (limited to 'u/process')
-rw-r--r-- | u/process/argv.ts | 42 | ||||
-rw-r--r-- | u/process/env.ts | 13 | ||||
-rw-r--r-- | u/process/index.ts | 1 | ||||
-rw-r--r-- | u/process/run.ts | 36 | ||||
-rw-r--r-- | u/process/signals.ts | 49 |
5 files changed, 96 insertions, 45 deletions
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 extends string>(k: string): k is K => k.startsWith('--'); @@ -13,22 +13,27 @@ export const getArg = <K extends string, V>( argv: Array<string>, whenValue: ArgHandler<V>, ): IEither<Error, V> => { - 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<Error, V>(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<Error, V>(<V>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<Error, Partial<Result>>, current: IEither<Error, readonly [Args[number], unknown]>) => + (acc: IEither<Error, Partial<Result>>, current: IEither<Error, [Args[number], unknown]>) => acc.flatMap((accValue) => current.mapRight(([key, value]) => ({ ...accValue, @@ -68,4 +73,5 @@ export const argv = < Either.right(<Partial<Result>>{}), ) .mapRight((result) => <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 = <V extends string>(name: V): IEither<Error, V> => - Either.fromFailable<Error, V | undefined>(() => 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 = <V extends string>(name: string): IOptional<V> => Optional.from(<V>process.env[name]); + +export const getRequiredEnv = <V extends string>(name: string): IEither<Error, V> => + Either.fromFailable(() => getEnv<V>(name).get()).mapLeft( + () => new Error(`environment variable "${name}" is required D:`), + ); type ObjectFromList<T extends ReadonlyArray<string>, V = string> = { [K in T extends ReadonlyArray<infer U> ? 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<Command, LogTraceSupplier>, + c: ITraceable<Command, LogMetricTraceSupplier>, options: { env?: Record<string, string>; clearEnv?: boolean } = {}, ): Promise<IEither<Error, string>> => 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<IEither<Error, StdStreams>> = Either.fromFailableAsync(exec(_exec, { env })); - return [p, `Command = ${_exec}`]; + return Either.fromFailableAsync<Error, StdStreams>(exec(_exec, { env })); }) .map( - TraceUtil.promiseify( - (tEitherProcess): IEither<Error, string> => - 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<TFailure> { + readonly close: SideEffect<SideEffect<TFailure | undefined>>; +} + +export class Signals { + public static async awaitClose<E extends Error>( + t: ITraceable<Closeable<E>, LogMetricTraceSupplier>, + ): Promise<IEither<Error, void>> { + const success: IEither<Error, void> = Either.right(<void>undefined); + return new Promise<IEither<Error, void>>((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<Error, void>(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)); + }); + } +} |