summaryrefslogtreecommitdiff
path: root/u/process
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2025-05-12 09:40:12 -0700
committerElizabeth <me@liz.coffee>2025-05-26 14:15:42 -0700
commitd51c9d74857aca3c2f172609297266968bc7f809 (patch)
tree64327f9cc4219729aa11af32d7d4c70cddfc2292 /u/process
parent30729a0cf707d9022bae0a7baaba77379dc31fd5 (diff)
downloadci-d51c9d74857aca3c2f172609297266968bc7f809.tar.gz
ci-d51c9d74857aca3c2f172609297266968bc7f809.zip
The big refactor TM
Diffstat (limited to 'u/process')
-rw-r--r--u/process/env.ts36
-rw-r--r--u/process/mod.ts3
-rw-r--r--u/process/run.ts64
-rw-r--r--u/process/validate_identifier.ts24
4 files changed, 127 insertions, 0 deletions
diff --git a/u/process/env.ts b/u/process/env.ts
new file mode 100644
index 0000000..0e41b4f
--- /dev/null
+++ b/u/process/env.ts
@@ -0,0 +1,36 @@
+import { Either, type IEither } from "@emprespresso/pengueno";
+
+export const getRequiredEnv = <V extends string>(name: V): IEither<Error, V> =>
+ Either
+ .fromFailable<Error, V>(() => Deno.env.get(name) as V) // 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;
+};
+
+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>),
+ );
diff --git a/u/process/mod.ts b/u/process/mod.ts
new file mode 100644
index 0000000..3f02d46
--- /dev/null
+++ b/u/process/mod.ts
@@ -0,0 +1,3 @@
+export * from "./env.ts";
+export * from "./run.ts";
+export * from "./validate_identifier.ts";
diff --git a/u/process/run.ts b/u/process/run.ts
new file mode 100644
index 0000000..4954438
--- /dev/null
+++ b/u/process/run.ts
@@ -0,0 +1,64 @@
+import {
+ Either,
+ type IEither,
+ type ITraceable,
+ LogLevel,
+ TraceUtil,
+} from "@emprespresso/pengueno";
+
+export type Command = string[] | string;
+type CommandOutputDecoded = {
+ code: number;
+ stdoutText: string;
+ stderrText: string;
+};
+
+export const getStdout = <Trace>(
+ c: ITraceable<Command, Trace>,
+ options: Deno.CommandOptions = {},
+): Promise<IEither<Error, string>> =>
+ c.bimap(TraceUtil.withFunctionTrace(getStdout))
+ .map((tCmd) => {
+ const cmd = tCmd.get();
+ tCmd.trace.trace(`:> im gonna run this command! ${cmd}`);
+ const [exec, ...args] = (typeof cmd === "string") ? cmd.split(" ") : cmd;
+ return new Deno.Command(exec, {
+ args,
+ stdout: "piped",
+ stderr: "piped",
+ ...options,
+ }).output();
+ })
+ .map((tOut) =>
+ Either.fromFailableAsync<Error, Deno.CommandOutput>(tOut.get())
+ )
+ .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(`o.o wat ${e}`);
+ return new Error(`${e}`);
+ })
+ .flatMap((decodedOutput): Either<Error, string> => {
+ const { code, stdoutText, stderrText } = decodedOutput;
+ tEitherOut.trace.addTrace(LogLevel.DEBUG).trace(
+ `stderr hehehe ${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();
diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts
new file mode 100644
index 0000000..32952a6
--- /dev/null
+++ b/u/process/validate_identifier.ts
@@ -0,0 +1,24 @@
+import { Either, type IEither } from "@emprespresso/pengueno";
+
+export const validateIdentifier = (token: string) => {
+ 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>,
+): 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);
+};