summaryrefslogtreecommitdiff
path: root/u/process
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-06-29 17:31:30 -0700
committerElizabeth Hunt <me@liz.coffee>2025-06-29 17:31:30 -0700
commit58be1809c46cbe517a18d86d0af52179dcc5cbf6 (patch)
tree9ccc678b3fd48c1a52fe501600dd2c2051740a55 /u/process
parentd4791f3d357634daf506fb8f91cc5332a794c421 (diff)
downloadci-58be1809c46cbe517a18d86d0af52179dcc5cbf6.tar.gz
ci-58be1809c46cbe517a18d86d0af52179dcc5cbf6.zip
Move to nodejs and also lots of significant refactoring that should've been broken up but idgaf
Diffstat (limited to 'u/process')
-rw-r--r--u/process/argv.ts42
-rw-r--r--u/process/env.ts13
-rw-r--r--u/process/index.ts1
-rw-r--r--u/process/run.ts36
-rw-r--r--u/process/signals.ts49
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));
+ });
+ }
+}