From dc4ac7742690f8f2bd759d57108ac4455e717fe9 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 20 Jul 2025 13:03:39 -0700 Subject: Mount src directory from path on host running worker container --- u/process/env.ts | 24 ++++++++++---------- u/process/exec.ts | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ u/process/index.ts | 2 +- u/process/run.ts | 40 ---------------------------------- u/trace/itrace.ts | 6 ++--- u/types/fn/callable.ts | 2 ++ u/types/fn/either.ts | 56 +++++++++++++++++++++++++++++++---------------- u/types/fn/optional.ts | 8 +++---- u/types/index.ts | 2 ++ u/types/misc.ts | 3 +++ 10 files changed, 122 insertions(+), 80 deletions(-) create mode 100644 u/process/exec.ts delete mode 100644 u/process/run.ts create mode 100644 u/types/misc.ts (limited to 'u') diff --git a/u/process/env.ts b/u/process/env.ts index 88fb490..f59fadf 100644 --- a/u/process/env.ts +++ b/u/process/env.ts @@ -1,27 +1,25 @@ -import { IOptional, Either, Optional, type IEither } from '@emprespresso/pengueno'; +import { IOptional, Either, Optional, type IEither, type ObjectFromList } from '@emprespresso/pengueno'; -export const getEnv = (name: string): IOptional => Optional.from(process.env[name]); +// type safe environment variables -export const getRequiredEnv = (name: string): IEither => - Either.fromFailable(() => getEnv(name).get()).mapLeft( +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:`), ); -type ObjectFromList, V = string> = { - [K in T extends ReadonlyArray ? U : never]: V; -}; - -export const getRequiredEnvVars = (vars: Array) => { +export const getRequiredEnvVars = (vars: Array): IEither> => { type Environment = ObjectFromList; const emptyEnvironment = Either.right({}); - const addTo = (env: Environment, key: V) => (val: string) => + const addTo = (env: Environment, key: V, val: string) => { ...env, [key]: val, }; - return Either.joinRight( - vars, - (envVar: V, environment: Environment) => getRequiredEnv(envVar).mapRight(addTo(environment, envVar)), + 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 new file mode 100644 index 0000000..a2cdbca --- /dev/null +++ b/u/process/exec.ts @@ -0,0 +1,59 @@ +import { + Either, + IEither, + type ITraceable, + LogLevel, + LogMetricTraceSupplier, + Metric, + TraceUtil, +} from '@emprespresso/pengueno'; +import { promisify } from 'node:util'; +import { exec as execCallback } from 'node:child_process'; +const exec = promisify(execCallback); + +export type Command = string[] | string; +export type StdStreams = { stdout: string; stderr: string }; + +export const CmdMetric = Metric.fromName('Exec').asResult(); +type Environment = Record; +type Options = { env?: Environment; clearEnv?: boolean }; +export const getStdout = ( + cmd: ITraceable, + options: Options = {}, +): 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(exec(_exec, { env })); + }) + .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 = {}, +): 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 index 6945a0f..2d74a5f 100644 --- a/u/process/index.ts +++ b/u/process/index.ts @@ -1,5 +1,5 @@ +export * from './exec.js'; 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 deleted file mode 100644 index 1d19129..0000000 --- a/u/process/run.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - Either, - IEither, - type ITraceable, - LogLevel, - LogMetricTraceSupplier, - Metric, - TraceUtil, -} from '@emprespresso/pengueno'; -import { promisify } from 'node:util'; -import { exec as execCallback } from 'node:child_process'; -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, - options: { env?: Record; clearEnv?: boolean } = {}, -): Promise> => - c - .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(exec(_exec, { env })); - }) - .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(); diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts index 9c33ad2..57c4419 100644 --- a/u/trace/itrace.ts +++ b/u/trace/itrace.ts @@ -28,8 +28,8 @@ export interface ITraceable { readonly map: <_T>(mapper: ITraceableMapper) => ITraceable<_T, Trace>; readonly bimap: <_T>(mapper: ITraceableMapper, Trace>) => ITraceable<_T, Trace>; readonly coExtend: <_T>( - mapper: ITraceableMapper, Trace>, - ) => ReadonlyArray>; + mapper: ITraceableMapper, Trace>, + ) => Array>; readonly peek: (peek: ITraceableMapper) => ITraceable; readonly traceScope: (mapper: ITraceableMapper) => ITraceable; @@ -51,7 +51,7 @@ export class TraceableImpl implements ITraceable { return new TraceableImpl(result, this.trace); } - public coExtend<_T>(mapper: ITraceableMapper, Trace>): ReadonlyArray> { + public coExtend<_T>(mapper: ITraceableMapper, Trace>): Array> { const results = mapper(this); return Array.from(results).map((result) => this.move(result)); } diff --git a/u/types/fn/callable.ts b/u/types/fn/callable.ts index 51756d7..60d747b 100644 --- a/u/types/fn/callable.ts +++ b/u/types/fn/callable.ts @@ -10,6 +10,8 @@ export interface Mapper extends Callable { (t: T): U; } +export interface Predicate extends Mapper {} + export interface BiMapper extends Callable { (t: T, u: U): R; } diff --git a/u/types/fn/either.ts b/u/types/fn/either.ts index aa67d41..80f32b4 100644 --- a/u/types/fn/either.ts +++ b/u/types/fn/either.ts @@ -1,18 +1,36 @@ -import { BiMapper, IOptional, type Mapper, Optional, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno'; +import { + BiMapper, + IOptional, + type Mapper, + Optional, + Predicate, + type Supplier, + Tagged, + isTagged, +} from '@emprespresso/pengueno'; export const IEitherTag = 'IEither' as const; export type IEitherTag = typeof IEitherTag; export const isEither = (o: unknown): o is IEither => isTagged(o, IEitherTag); export interface IEither extends Tagged { - readonly mapBoth: <_E, _T>(errBranch: Mapper, okBranch: Mapper) => IEither<_E, _T>; - readonly fold: <_T>(leftFolder: Mapper, rightFolder: Mapper) => _T; readonly left: Supplier>; readonly right: Supplier>; - readonly moveRight: <_T>(t: _T) => IEither; + readonly mapRight: <_T>(mapper: Mapper) => IEither; + readonly filter: (mapper: Predicate) => IEither; readonly mapLeft: <_E>(mapper: Mapper) => IEither<_E, T>; + readonly mapBoth: <_E, _T>(errBranch: Mapper, okBranch: Mapper) => IEither<_E, _T>; + readonly flatMap: <_T>(mapper: Mapper>) => IEither; readonly flatMapAsync: <_T>(mapper: Mapper>>) => Promise>; + + readonly moveRight: <_T>(t: _T) => IEither; + readonly fold: <_T>(leftFolder: Mapper, rightFolder: Mapper) => _T; + readonly joinRight: (other: IEither, mapper: BiMapper) => IEither; + readonly joinRightAsync: ( + other: Supplier>> | Promise>, + mapper: BiMapper, + ) => Promise>; } const ELeftTag = 'E.Left' as const; @@ -62,6 +80,11 @@ export class Either extends _Tagged implements IEither { return Either.left(this.self.err); } + public filter(mapper: Predicate): IEither { + if (isLeft(this.self)) return Either.left(this.self.err); + return Either.fromFailable(() => this.right().filter(mapper).get()); + } + public async flatMapAsync<_T>(mapper: Mapper>>): Promise> { if (isLeft(this.self)) return Promise.resolve(Either.left(this.self.err)); return await mapper(this.self.ok).catch((err) => Either.left(err)); @@ -82,23 +105,18 @@ export class Either extends _Tagged implements IEither { return Optional.none(); } - static joinRight( - arr: Array, - mapper: BiMapper>, - init: IEither, - ): IEither { - return arr.reduce((acc: IEither, x: K) => acc.flatMap((t) => mapper(x, t)), init); + public joinRight(other: IEither, mapper: BiMapper) { + return this.flatMap((t) => other.mapRight((o) => mapper(o, t))); } - static joinRightAsync( - arr: Array, - mapper: BiMapper>>, - init: IEither, - ): Promise> { - return arr.reduce( - (acc: Promise>, x: K) => acc.then((res) => res.flatMapAsync((t) => mapper(x, t))), - Promise.resolve(init), - ); + public joinRightAsync( + other: Supplier>> | Promise>, + mapper: BiMapper, + ) { + return this.flatMapAsync(async (t) => { + const o = typeof other === 'function' ? other() : other; + return o.then((other) => other.mapRight((o) => mapper(o, t))); + }); } static left(e: E): IEither { diff --git a/u/types/fn/optional.ts b/u/types/fn/optional.ts index 3396a45..504e496 100644 --- a/u/types/fn/optional.ts +++ b/u/types/fn/optional.ts @@ -1,4 +1,4 @@ -import { type Mapper, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno'; +import { type Mapper, Predicate, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno'; export type MaybeGiven = T | undefined | null; @@ -9,7 +9,7 @@ export class IOptionalEmptyError extends Error {} export interface IOptional = NonNullable> extends Tagged, Iterable { readonly move: <_T>(t: MaybeGiven<_T>) => IOptional<_T>; readonly map: <_T>(mapper: Mapper>) => IOptional<_T>; - readonly filter: (mapper: Mapper) => IOptional; + readonly filter: (mapper: Predicate) => IOptional; readonly flatMap: <_T>(mapper: Mapper>>) => IOptional<_T>; readonly orSome: (supplier: Supplier>) => IOptional; readonly get: Supplier; @@ -48,11 +48,11 @@ export class Optional = NonNullable> extends _Tag } public get(): T { - if (isNone(this.self)) throw new IOptionalEmptyError('empty value'); + if (isNone(this.self)) throw new IOptionalEmptyError('called get() on None optional'); return this.self.value; } - public filter(mapper: Mapper): IOptional { + public filter(mapper: Predicate): IOptional { if (isNone(this.self) || !mapper(this.self.value)) return Optional.none(); return Optional.some(this.self.value); } diff --git a/u/types/index.ts b/u/types/index.ts index c68cedc..fc1b15a 100644 --- a/u/types/index.ts +++ b/u/types/index.ts @@ -1,3 +1,5 @@ +export * from './misc.js'; + export * from './object.js'; export * from './tagged.js'; diff --git a/u/types/misc.ts b/u/types/misc.ts new file mode 100644 index 0000000..77833c4 --- /dev/null +++ b/u/types/misc.ts @@ -0,0 +1,3 @@ +export type ObjectFromList, V = string> = { + [K in T extends ReadonlyArray ? U : never]: V; +}; -- cgit v1.2.3-70-g09d2