diff options
Diffstat (limited to 'u/process')
-rw-r--r-- | u/process/argv.ts | 77 | ||||
-rw-r--r-- | u/process/env.ts | 53 | ||||
-rw-r--r-- | u/process/index.ts | 4 | ||||
-rw-r--r-- | u/process/mod.ts | 4 | ||||
-rw-r--r-- | u/process/run.ts | 102 | ||||
-rw-r--r-- | u/process/validate_identifier.ts | 23 |
6 files changed, 114 insertions, 149 deletions
diff --git a/u/process/argv.ts b/u/process/argv.ts index 45f8ff0..dcdba85 100644 --- a/u/process/argv.ts +++ b/u/process/argv.ts @@ -1,7 +1,6 @@ -import { Either, type Mapper, type IEither } from "@emprespresso/pengueno"; +import { Either, type Mapper, type IEither } from '@emprespresso/pengueno'; -export const isArgKey = <K extends string>(k: string): k is K => - k.startsWith("--"); +export const isArgKey = <K extends string>(k: string): k is K => k.startsWith('--'); interface ArgHandler<V> { absent?: V; @@ -10,42 +9,42 @@ interface ArgHandler<V> { } export const getArg = <K extends string, V>( - arg: K, - argv: Array<string>, - whenValue: ArgHandler<V>, + arg: K, + 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; + 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)); + return Either.left(new Error('no value specified for ' + arg)); } return Either.right(value); }; type MappedArgs< - Args extends ReadonlyArray<string>, - Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>> + Args extends ReadonlyArray<string>, + Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>, > = { - [K in Args[number]]: K extends keyof Handlers - ? Handlers[K] extends ArgHandler<infer T> - ? T - : string - : string; + [K in Args[number]]: K extends keyof Handlers ? (Handlers[K] extends ArgHandler<infer T> ? T : string) : string; }; export const argv = < - const Args extends ReadonlyArray<string>, - const Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>> + const Args extends ReadonlyArray<string>, + const Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>, >( args: Args, handlers?: Handlers, - argv = Deno.args, + argv = process.argv.slice(2), ): IEither<Error, MappedArgs<Args, Handlers>> => { type Result = MappedArgs<Args, Handlers>; @@ -53,20 +52,20 @@ export const argv = < const processArg = (arg: Args[number]): IEither<Error, [Args[number], unknown]> => { const handler = handlers?.[arg] ?? defaultHandler; - return getArg(arg, argv, handler).mapRight(value => [arg, value] as const); + return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const); }; - const argResults = args.map(processArg); - - return argResults.reduce( - (acc: IEither<Error, Partial<Result>>, current: IEither<Error, readonly [Args[number], unknown]>) => { - return acc.flatMap(accValue => - current.mapRight(([key, value]) => ({ - ...accValue, - [key]: value - })) - ); - }, - Either.right({} as Partial<Result>) - ).mapRight(result => result as Result); + return args + .map(processArg) + .reduce( + (acc: IEither<Error, Partial<Result>>, current: IEither<Error, readonly [Args[number], unknown]>) => + acc.flatMap((accValue) => + current.mapRight(([key, value]) => ({ + ...accValue, + [key]: value, + })), + ), + Either.right(<Partial<Result>>{}), + ) + .mapRight((result) => <Result>result); }; diff --git a/u/process/env.ts b/u/process/env.ts index 5ba4189..1e4fd32 100644 --- a/u/process/env.ts +++ b/u/process/env.ts @@ -1,37 +1,30 @@ -import { Either, type IEither } from "@emprespresso/pengueno"; +import { Either, type IEither } from '@emprespresso/pengueno'; export const getRequiredEnv = <V extends string>(name: V): IEither<Error, V> => - Either.fromFailable<Error, V | undefined>( - () => Deno.env.get(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:`)), - ); + 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:`)), + ); type ObjectFromList<T extends ReadonlyArray<string>, V = string> = { - [K in T extends ReadonlyArray<infer U> ? U : never]: V; + [K in T extends ReadonlyArray<infer U> ? U : never]: V; }; export const getRequiredEnvVars = <V extends string>(vars: ReadonlyArray<V>) => - vars - .map((envVar) => [envVar, getRequiredEnv(envVar)] as [V, IEither<Error, V>]) - .reduce( - ( - acc: IEither<Error, ObjectFromList<typeof vars>>, - x: [V, IEither<Error, V>], - ) => { - const [envVar, eitherVal] = x; - return acc.flatMap((args) => { - return eitherVal.mapRight( - (envValue) => - ({ - ...args, - [envVar]: envValue, - }) as ObjectFromList<typeof vars>, - ); - }); - }, - Either.right({} as ObjectFromList<typeof vars>), - ); + vars + .map((envVar) => [envVar, getRequiredEnv(envVar)] as [V, IEither<Error, V>]) + .reduce( + (acc: IEither<Error, ObjectFromList<typeof vars>>, x: [V, IEither<Error, V>]) => { + const [envVar, eitherVal] = x; + return acc.flatMap((args) => { + return eitherVal.mapRight( + (envValue) => + ({ + ...args, + [envVar]: envValue, + }) as ObjectFromList<typeof vars>, + ); + }); + }, + Either.right({} as ObjectFromList<typeof vars>), + ); diff --git a/u/process/index.ts b/u/process/index.ts new file mode 100644 index 0000000..4ffbf2a --- /dev/null +++ b/u/process/index.ts @@ -0,0 +1,4 @@ +export * from './env.js'; +export * from './run.js'; +export * from './validate_identifier.js'; +export * from './argv.js'; diff --git a/u/process/mod.ts b/u/process/mod.ts deleted file mode 100644 index 211e9a7..0000000 --- a/u/process/mod.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./env.ts"; -export * from "./run.ts"; -export * from "./validate_identifier.ts"; -export * from "./argv.ts"; diff --git a/u/process/run.ts b/u/process/run.ts index abe143c..e3c4c3d 100644 --- a/u/process/run.ts +++ b/u/process/run.ts @@ -1,68 +1,46 @@ import { - Either, - type IEither, - type ITraceable, - LogLevel, - type LogTraceSupplier, - TraceUtil, -} from "@emprespresso/pengueno"; + Either, + type IEither, + type ITraceable, + LogLevel, + type LogTraceSupplier, + 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; -type CommandOutputDecoded = { - code: number; - stdoutText: string; - stderrText: string; -}; +export type StdStreams = { stdout: string; stderr: string }; export const getStdout = ( - c: ITraceable<Command, LogTraceSupplier>, - options: Deno.CommandOptions = {}, + c: ITraceable<Command, LogTraceSupplier>, + options: { env?: Record<string, string>; clearEnv?: boolean } = {}, ): Promise<IEither<Error, string>> => - c - .bimap(TraceUtil.withFunctionTrace(getStdout)) - .map((tCmd) => { - const cmd = tCmd.get(); - tCmd.trace.trace(`Command = ${cmd} :> im gonna run this command! `); - const [exec, ...args] = typeof cmd === "string" ? cmd.split(" ") : cmd; - return new Deno.Command(exec, { - args, - stdout: "piped", - stderr: "piped", - ...options, - }); - }) - .map((tCmd) => - Either.fromFailableAsync<Error, Deno.CommandOutput>(() => - tCmd.get().output(), - ), - ) - .map( - TraceUtil.promiseify((tEitherOut) => - tEitherOut.get().flatMap(({ code, stderr, stdout }) => - Either.fromFailable<Error, CommandOutputDecoded>(() => { - const stdoutText = new TextDecoder().decode(stdout); - const stderrText = new TextDecoder().decode(stderr); - return { code, stdoutText, stderrText }; - }) - .mapLeft((e) => { - tEitherOut.trace.addTrace(LogLevel.ERROR).trace(e); - return e; - }) - .flatMap((decodedOutput): IEither<Error, string> => { - const { code, stdoutText, stderrText } = decodedOutput; - if (stderrText) { - tEitherOut.trace - .addTrace(LogLevel.DEBUG) - .trace(`stderr: ${stderrText}`); - } - if (code !== 0) { - const msg = `i weceived an exit code of ${code} i wanna zewoooo :<`; - tEitherOut.trace.addTrace(LogLevel.ERROR).trace(msg); - return Either.left(new Error(msg)); - } - return Either.right(stdoutText); - }), - ), - ), - ) - .get(); + c + .bimap(TraceUtil.withFunctionTrace(getStdout)) + .bimap((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}`]; + }) + .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); + }), + ), + ) + .get(); diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts index 4e93728..1ff3791 100644 --- a/u/process/validate_identifier.ts +++ b/u/process/validate_identifier.ts @@ -1,23 +1,18 @@ -import { Either, type IEither } from "@emprespresso/pengueno"; +import { Either, type IEither } from '@emprespresso/pengueno'; export const validateIdentifier = (token: string) => { - return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes(".."); + return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes('..'); }; // ensure {@param obj} is a Record<string, string> with stuff that won't // have the potential for shell injection, just to be super safe. type InvalidEntry<K, T> = [K, T]; -export const validateExecutionEntries = < - T, - K extends symbol | number | string = symbol | number | string, ->( - obj: Record<K, T>, +export const validateExecutionEntries = <T, K extends symbol | number | string = symbol | number | string>( + obj: Record<K, T>, ): IEither<Array<InvalidEntry<K, T>>, Record<string, string>> => { - const invalidEntries = <Array<InvalidEntry<K, T>>>( - Object.entries(obj).filter( - (e) => !e.every((x) => typeof x === "string" && validateIdentifier(x)), - ) - ); - if (invalidEntries.length > 0) return Either.left(invalidEntries); - return Either.right(<Record<string, string>>obj); + const invalidEntries = <Array<InvalidEntry<K, T>>>( + Object.entries(obj).filter((e) => !e.every((x) => typeof x === 'string' && validateIdentifier(x))) + ); + if (invalidEntries.length > 0) return Either.left(invalidEntries); + return Either.right(<Record<string, string>>obj); }; |