From 1d66a0f58e4ebcdf4f42c9d78f82a1ab49a2cf11 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Tue, 13 May 2025 18:58:45 -0700 Subject: snapshot! --- deno.json | 2 +- hooks/job/queue.ts | 0 hooks/main.ts | 1 + hooks/server.ts | 61 +----------------- hooks/server/activity/health.ts | 0 hooks/server/activity/hook.ts | 0 hooks/server/activity/mod.ts | 8 +++ hooks/server/filter/json.ts | 25 ++++++++ hooks/server/filter/method.ts | 22 +++++++ hooks/server/filter/mod.ts | 18 ++++++ hooks/server/mod.ts | 0 u/deno.json | 6 ++ u/fn/callable.ts | 15 +++++ u/fn/either.ts | 62 +++++++++++++++++++ u/fn/mod.ts | 2 + u/leftpadesque/debug.ts | 11 ++++ u/leftpadesque/mod.ts | 3 + u/leftpadesque/object.ts | 2 + u/leftpadesque/prepend.ts | 4 ++ u/mod.ts | 4 ++ u/process/env.ts | 10 +++ u/process/mod.ts | 3 + u/process/run.ts | 40 ++++++++++++ u/process/validate_identifier.ts | 17 +++++ u/server/mod.ts | 0 u/trace/itrace.ts | 72 ++++++++++++++++++++++ u/trace/logger.ts | 86 ++++++++++++++++++++++++++ u/trace/mod.ts | 3 + u/trace/trace.ts | 34 ++++++++++ utils/either.ts | 48 --------------- utils/env.ts | 5 -- utils/isObject.ts | 2 - utils/mod.ts | 8 --- utils/prepend.ts | 4 -- utils/run.ts | 40 ------------ utils/trace.ts | 130 --------------------------------------- utils/validate_identifier.ts | 17 ----- 37 files changed, 450 insertions(+), 315 deletions(-) create mode 100644 hooks/job/queue.ts create mode 100644 hooks/main.ts create mode 100644 hooks/server/activity/health.ts create mode 100644 hooks/server/activity/hook.ts create mode 100644 hooks/server/activity/mod.ts create mode 100644 hooks/server/filter/json.ts create mode 100644 hooks/server/filter/method.ts create mode 100644 hooks/server/filter/mod.ts create mode 100644 hooks/server/mod.ts create mode 100644 u/deno.json create mode 100644 u/fn/callable.ts create mode 100644 u/fn/either.ts create mode 100644 u/fn/mod.ts create mode 100644 u/leftpadesque/debug.ts create mode 100644 u/leftpadesque/mod.ts create mode 100644 u/leftpadesque/object.ts create mode 100644 u/leftpadesque/prepend.ts create mode 100644 u/mod.ts create mode 100644 u/process/env.ts create mode 100644 u/process/mod.ts create mode 100644 u/process/run.ts create mode 100644 u/process/validate_identifier.ts create mode 100644 u/server/mod.ts create mode 100644 u/trace/itrace.ts create mode 100644 u/trace/logger.ts create mode 100644 u/trace/mod.ts create mode 100644 u/trace/trace.ts delete mode 100644 utils/either.ts delete mode 100644 utils/env.ts delete mode 100644 utils/isObject.ts delete mode 100644 utils/mod.ts delete mode 100644 utils/prepend.ts delete mode 100644 utils/run.ts delete mode 100644 utils/trace.ts delete mode 100644 utils/validate_identifier.ts diff --git a/deno.json b/deno.json index 1dd298e..7c696db 100644 --- a/deno.json +++ b/deno.json @@ -1,3 +1,3 @@ { - "workspace": ["./model", "./worker", "./hooks", "./utils"] + "workspace": ["./model", "./worker", "./hooks", "./utils", "./lizutils"] } diff --git a/hooks/job/queue.ts b/hooks/job/queue.ts new file mode 100644 index 0000000..e69de29 diff --git a/hooks/main.ts b/hooks/main.ts new file mode 100644 index 0000000..1348e57 --- /dev/null +++ b/hooks/main.ts @@ -0,0 +1 @@ +#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run diff --git a/hooks/server.ts b/hooks/server.ts index 000c391..9a3f716 100755 --- a/hooks/server.ts +++ b/hooks/server.ts @@ -1,5 +1,3 @@ -#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run - import { Either, getRequiredEnv, @@ -20,61 +18,6 @@ interface IHealthCheckActivity { healthCheck(req: R): Promise; } -class HealthCheckActivity implements IHealthCheckActivity> { - public async healthCheck( - req: Traceable, - ) { - return await req.bimap(Traceable.withClassTrace(this)) - .map(async (r) => { - const { logger } = r; - try { - getRequiredEnv("LAMINAR_HOST"); - await getStdout(r.map(() => ["laminarc", "show-jobs"])); - const msg = `think im healthy!! (✿˘◡˘) ready to do work~\n`; - logger.log(msg); - return new Response( - msg, - { status: 200 }, - ); - } catch (error) { - logger.error(error); - return new Response( - "oh no, i need to eat more vegetables (。•́︿•̀。)...\n", - { status: 500 }, - ); - } - }).item; - } -} - -const aPost = (req: Traceable): IEither => { - const { item: request, logger: _logger } = req; - const logger = _logger.addTracer(() => "[aPost]"); - - if (request.method !== "POST") { - const msg = "that's not how you pet me (⋟﹏⋞) try post instead~"; - logger.warn(msg); - return Either.left(new Response(msg + "\n", { status: 405 })); - } - return Either.right(request); -}; - -type JsonTransformer = (json: Traceable) => Either; -const aJson = - (jsonTransformer: JsonTransformer) => - async (r: Traceable): Promise> => { - const { item: request, logger } = r; - try { - return Either.right(await request.json()) - .mapRight(r.move) - .flatMap(jsonTransformer); - } catch (_e) { - const err = "seems to be invalid JSON (>//<) can you fix?"; - logger.warn(err); - return Either.left(err); - } - }; - interface IJobHookActivity { processHook(req: R): Promise; } @@ -82,9 +25,7 @@ type GetJobRequest = { jobType: string; args: unknown }; class JobHookActivityImpl implements IJobHookActivity> { constructor(private readonly queuer: IJobQueuer>) {} - private getJob( - u: Traceable, - ): Either { + private getJob(u: Traceable): Either { const { logger: _logger, item } = u; const logger = _logger.addTracer(() => "[getJob]"); const couldBeJsonJob = isObject(item) && "arguments" in item && diff --git a/hooks/server/activity/health.ts b/hooks/server/activity/health.ts new file mode 100644 index 0000000..e69de29 diff --git a/hooks/server/activity/hook.ts b/hooks/server/activity/hook.ts new file mode 100644 index 0000000..e69de29 diff --git a/hooks/server/activity/mod.ts b/hooks/server/activity/mod.ts new file mode 100644 index 0000000..0dee57b --- /dev/null +++ b/hooks/server/activity/mod.ts @@ -0,0 +1,8 @@ +import type { RequestFilter } from "../filter/mod.ts"; + +export class r200 extends Response { + public override readonly status = 200; +} + +export interface IActivity extends RequestFilter { +} diff --git a/hooks/server/filter/json.ts b/hooks/server/filter/json.ts new file mode 100644 index 0000000..bcdd3ee --- /dev/null +++ b/hooks/server/filter/json.ts @@ -0,0 +1,25 @@ +import { Either, type IEither, type Traceable } from "@emprespresso/utils"; +import type { RequestFilter } from "./mod.ts"; + +type JsonTransformer = ( + json: Traceable, +) => IEither; +export const json = ( + jsonTransformer: JsonTransformer, +): RequestFilter => +async (r: Traceable) => { + const { item: request, logger: _logger } = r; + const logger = _logger.addTracer(() => "[jsonVerification]"); + + const getJson = request.json().catch((errReason) => { + const err = "seems to be invalid JSON (>//<) can you fix?"; + logger.warn(err, errReason); + return new Error(err); + }); + return (await Either.fromAsync(getJson)) + .mapRight(r.move).flatMap(jsonTransformer) + .mapLeft((err) => { + logger.warn(err); + return new Response(err.message, { status: 400 }); + }); +}; diff --git a/hooks/server/filter/method.ts b/hooks/server/filter/method.ts new file mode 100644 index 0000000..07fa5ee --- /dev/null +++ b/hooks/server/filter/method.ts @@ -0,0 +1,22 @@ +import { Either, type Traceable } from "@liz-ci/utils"; +import type { RequestFilter } from "./mod.ts"; + +type HttpMethod = "POST" | "GET" | "HEAD" | "PUT" | "DELETE"; +export const requireMethod = + (methods: Array): RequestFilter => + (req: Traceable) => { + const { item: request, logger: _logger } = req; + const logger = _logger.addTracer(() => "[aPost]"); + + const { method: _method } = request; + const method = _method; + if (!methods.includes(method)) { + const msg = "that's not how you pet me (⋟﹏⋞)~"; + logger.warn(msg); + return Promise.resolve(Either.left( + new Response(msg + "\n", { status: 405 }), + )); + } + + return Promise.resolve(Either.right(method)); + }; diff --git a/hooks/server/filter/mod.ts b/hooks/server/filter/mod.ts new file mode 100644 index 0000000..bedc678 --- /dev/null +++ b/hooks/server/filter/mod.ts @@ -0,0 +1,18 @@ +import type { + IEither, + ITraceable, + ITraceableLogger, + TraceableLogger, +} from "@liz-ci/utils"; + +export interface RequestFilter< + T, + L extends ITraceableLogger = TraceableLogger, + RIn = ITraceable, + Err = Response, +> { + (req: RIn): Promise>; +} + +export * from "./method.ts"; +export * from "./json.ts"; diff --git a/hooks/server/mod.ts b/hooks/server/mod.ts new file mode 100644 index 0000000..e69de29 diff --git a/u/deno.json b/u/deno.json new file mode 100644 index 0000000..46e74d6 --- /dev/null +++ b/u/deno.json @@ -0,0 +1,6 @@ +{ + "name": "@emprespresso/utils", + "version": "0.1.0", + "exports": "./mod.ts", + "workspace": ["./*"] +} diff --git a/u/fn/callable.ts b/u/fn/callable.ts new file mode 100644 index 0000000..2749947 --- /dev/null +++ b/u/fn/callable.ts @@ -0,0 +1,15 @@ +// deno-lint-ignore no-explicit-any +export interface Callable { + (...args: Array): T; +} + +export interface Supplier extends Callable { + (): T; +} + +export interface SideEffect extends Callable { +} + +export interface Mapper extends Callable { + (t: T): U; +} diff --git a/u/fn/either.ts b/u/fn/either.ts new file mode 100644 index 0000000..eaf77fd --- /dev/null +++ b/u/fn/either.ts @@ -0,0 +1,62 @@ +import type { Mapper, Supplier } from "./mod.ts"; + +export interface IEither { + mapBoth: ( + errBranch: Mapper, + okBranch: Mapper, + ) => IEither; + flatMap: (mapper: Mapper>) => IEither; + mapRight: (mapper: Mapper) => IEither; + mapLeft: (mapper: Mapper) => IEither; +} + +export class Either implements IEither { + private constructor(private readonly err?: E, private readonly ok?: T) {} + + public mapBoth( + errBranch: Mapper, + okBranch: Mapper, + ): Either { + if (this.err) return Either.left(errBranch(this.err)); + return Either.right(okBranch(this.ok!)); + } + + public flatMap(mapper: Mapper>) { + if (this.ok) return mapper(this.ok); + return Either.left(this.err!); + } + + public mapRight(mapper: Mapper): IEither { + if (this.ok) return Either.right(mapper(this.ok)); + return Either.left(this.err!); + } + + public mapLeft(mapper: Mapper) { + if (this.err) return Either.left(mapper(this.err)); + return Either.right(this.ok!); + } + + static left(e: E) { + return new Either(e); + } + + static right(t: T) { + return new Either(undefined, t); + } + + static fromFailable(s: Supplier) { + try { + return Either.right(s()); + } catch (e) { + return Either.left(e as E); + } + } + + static async fromFailableAsync(s: Promise) { + try { + return Either.right(await s); + } catch (e) { + return Either.left(e as E); + } + } +} diff --git a/u/fn/mod.ts b/u/fn/mod.ts new file mode 100644 index 0000000..f0fbe88 --- /dev/null +++ b/u/fn/mod.ts @@ -0,0 +1,2 @@ +export * from "./callable.ts"; +export * from "./either.ts"; diff --git a/u/leftpadesque/debug.ts b/u/leftpadesque/debug.ts new file mode 100644 index 0000000..a9da1f3 --- /dev/null +++ b/u/leftpadesque/debug.ts @@ -0,0 +1,11 @@ +const _hasEnv = !Deno.permissions.querySync({ name: "env" }); + +const _env: "development" | "production" = + _hasEnv && (Deno.env.get("ENVIRONMENT") ?? "").toLowerCase().includes("prod") + ? "production" + : "development"; +export const isProd = () => _env === "production"; + +const _debug = !isProd() || (_hasEnv && + ["y", "t"].some((Deno.env.get("DEBUG") ?? "").toLowerCase().startsWith)); +export const isDebug = () => _debug; diff --git a/u/leftpadesque/mod.ts b/u/leftpadesque/mod.ts new file mode 100644 index 0000000..801846a --- /dev/null +++ b/u/leftpadesque/mod.ts @@ -0,0 +1,3 @@ +export * from "./object.ts"; +export * from "./prepend.ts"; +export * from "./debug.ts"; diff --git a/u/leftpadesque/object.ts b/u/leftpadesque/object.ts new file mode 100644 index 0000000..73f7f80 --- /dev/null +++ b/u/leftpadesque/object.ts @@ -0,0 +1,2 @@ +export const isObject = (o: unknown): o is object => + typeof o === "object" && !Array.isArray(o) && !!o; diff --git a/u/leftpadesque/prepend.ts b/u/leftpadesque/prepend.ts new file mode 100644 index 0000000..9b77aff --- /dev/null +++ b/u/leftpadesque/prepend.ts @@ -0,0 +1,4 @@ +export const prependWith = (arr: string[], prep: string) => + Array(arr.length * 2).fill(0) + .map((_, i) => i % 2 === 0) + .map((isPrep, i) => isPrep ? prep : arr[i]); diff --git a/u/mod.ts b/u/mod.ts new file mode 100644 index 0000000..fab6804 --- /dev/null +++ b/u/mod.ts @@ -0,0 +1,4 @@ +export * from "./fn/mod.ts"; +export * from "./leftpadesque/mod.ts"; +export * from "./process/mod.ts"; +export * from "./trace/mod.ts"; diff --git a/u/process/env.ts b/u/process/env.ts new file mode 100644 index 0000000..e80ec4a --- /dev/null +++ b/u/process/env.ts @@ -0,0 +1,10 @@ +import { Either, type IEither } from "@emprespresso/utils"; + +export const getRequiredEnv = (name: string): IEither => + Either.fromFailable(() => { + const value = Deno.env.get(name); // could throw when no permission. + if (!value) { + throw new Error(`environment variable "${name}" is required D:`); + } + return value; + }); 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..6dc37d0 --- /dev/null +++ b/u/process/run.ts @@ -0,0 +1,40 @@ +import { Either, type Traceable } from "@emprespresso/utils"; + +export class ProcessError extends Error {} +export const getStdout = async ( + { item: cmd, logger: _logger }: Traceable, + options: Deno.CommandOptions = {}, +): Promise> => { + const logger = _logger.addTracer(() => "[getStdout]"); + + logger.log(`:> im gonna run this command!`, cmd); + const [exec, ...args] = (typeof cmd === "string") ? cmd.split(" ") : cmd; + const command = new Deno.Command(exec, { + args, + stdout: "piped", + stderr: "piped", + ...options, + }); + + try { + const { code, stdout, stderr } = await command.output(); + const stdoutText = new TextDecoder().decode(stdout); + const stderrText = new TextDecoder().decode(stderr); + + if (code !== 0) { + logger.error(`i weceived an exit code of ${code} i wanna zeroooo :<`); + return Either.left( + new ProcessError(`command failed\n${stderrText}`), + ); + } + + logger.log("yay! i got code 0 :3", cmd); + return Either.right(stdoutText); + } catch (e) { + logger.error(`o.o wat`, e); + if (e instanceof Error) { + return Either.left(e); + } + throw new Error("unknown error " + e); + } +}; diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts new file mode 100644 index 0000000..ec8b77b --- /dev/null +++ b/u/process/validate_identifier.ts @@ -0,0 +1,17 @@ +import { Either } from "./mod.ts"; + +export const validateIdentifier = (token: string) => { + return (/^[a-zA-Z0-9_\-:. \/]+$/).test(token) && !token.includes(".."); +}; + +// ensure {@param obj} is a Record with stuff that won't +// have the potential for shell injection, just to be super safe. +export const validateExecutionEntries = ( + obj: Record, +): Either, Record> => { + const invalidEntries = Object.entries(obj).filter((e) => + !e.every((x) => typeof x === "string" && validateIdentifier(x)) + ); + if (invalidEntries.length > 0) return Either.left(invalidEntries); + return Either.right(> obj); +}; diff --git a/u/server/mod.ts b/u/server/mod.ts new file mode 100644 index 0000000..e69de29 diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts new file mode 100644 index 0000000..b483067 --- /dev/null +++ b/u/trace/itrace.ts @@ -0,0 +1,72 @@ +import { Mapper, SideEffect } from "../fn/mod.ts"; + +export interface ITrace { + addTrace: Mapper>; + trace: SideEffect; +} + +export type ITraceableTuple = [T, Trace]; +export type ITraceableMapper> = ( + w: W, +) => U; + +export interface ITraceable { + readonly item: T; + readonly trace: ITrace; + + move(u: U): ITraceable; + map: ( + mapper: ITraceableMapper, + ) => ITraceable; + bimap: ( + mapper: ITraceableMapper>, + ) => ITraceable; + peek: (peek: ITraceableMapper) => ITraceable; + flatMap: ( + mapper: ITraceableMapper>, + ) => ITraceable; + flatMapAsync( + mapper: ITraceableMapper>>, + ): ITraceable, Trace>; +} + +export class TraceableImpl implements ITraceable { + protected constructor( + readonly item: T, + readonly trace: ITrace, + ) {} + + public map(mapper: ITraceableMapper) { + const result = mapper(this); + return new TraceableImpl(result, this.trace); + } + + public flatMap( + mapper: ITraceableMapper>, + ): ITraceable { + return mapper(this); + } + + public flatMapAsync( + mapper: ITraceableMapper>>, + ): ITraceable, L> { + return new TraceableImpl( + mapper(this).then(({ item }) => item), + this.trace, + ); + } + + public peek(peek: ITraceableMapper) { + peek(this); + return this; + } + + public move(t: Tt): ITraceable { + return this.map(() => t); + } + + public bimap(mapper: ITraceableMapper>) { + const [item, trace] = mapper(this); + return new TraceableImpl(item, this.trace.addTrace(trace)); + } +} diff --git a/u/trace/logger.ts b/u/trace/logger.ts new file mode 100644 index 0000000..79da367 --- /dev/null +++ b/u/trace/logger.ts @@ -0,0 +1,86 @@ +import { + isDebug, + isObject, + type ITrace, + type SideEffect, + type Supplier, +} from "@emprespresso/utils"; + +export interface ILogger { + log: (...args: unknown[]) => void; + debug: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; +} +export type ILoggerLevel = "UNKNOWN" | "INFO" | "WARN" | "DEBUG" | "ERROR"; +const logLevelOrder: Array = ["DEBUG", "INFO", "WARN", "ERROR"]; +const defaultAllowedLevels = () => + [ + "UNKNOWN", + ...(isDebug() ? ["DEBUG"] : []), + "INFO", + "WARN", + "ERROR", + ] as Array; + +export const logWithLevel = ( + logger: ILogger, + level: ILoggerLevel, +): SideEffect => { + switch (level) { + case "UNKNOWN": + case "INFO": + return logger.log; + case "DEBUG": + return logger.debug; + case "WARN": + return logger.warn; + case "ERROR": + return logger.error; + } +}; + +export const LoggerImpl = console; + +export type LogTraceSupplier = string | Supplier | { + level: ILoggerLevel; +}; + +const foldTraces = (traces: Array) => { + const { line, level } = traces.reduce( + (acc: { line: string; level: number }, t) => { + if (isObject(t) && "level" in t) { + return { + ...acc, + level: Math.max(logLevelOrder.indexOf(t.level), acc.level), + }; + } + const prefix = [ + acc.line, + typeof t === "function" ? t() : t, + ].join(" "); + return { ...acc, prefix }; + }, + { line: "", level: -1 }, + ); + return { line, level: logLevelOrder[level] ?? "UNKNOWN" }; +}; + +const defaultTrace = () => `[${new Date().toISOString()}]`; +export const LogTrace = ( + logger: ILogger, + traces: Array = [defaultTrace], + allowedLevels: Supplier> = defaultAllowedLevels, + defaultLevel: ILoggerLevel = "INFO", +): ITrace => { + return { + addTrace: (trace: LogTraceSupplier) => + LogTrace(logger, traces.concat(trace)), + trace: (trace: LogTraceSupplier) => { + const { line, level: _level } = foldTraces(traces.concat(trace)); + if (!allowedLevels().includes(_level)) return; + const level = _level === "UNKNOWN" ? defaultLevel : _level; + logWithLevel(logger, level)(`[${level}]${line}`); + }, + }; +}; diff --git a/u/trace/mod.ts b/u/trace/mod.ts new file mode 100644 index 0000000..9c42858 --- /dev/null +++ b/u/trace/mod.ts @@ -0,0 +1,3 @@ +export * from "./itrace.ts"; +export * from "./logger.ts"; +export * from "./trace.ts"; diff --git a/u/trace/trace.ts b/u/trace/trace.ts new file mode 100644 index 0000000..5d5c59b --- /dev/null +++ b/u/trace/trace.ts @@ -0,0 +1,34 @@ +import type { Callable } from "@emprespresso/utils"; +import { + type ITraceableMapper, + type ITraceableTuple, + TraceableImpl, + TraceableLogger, +} from "./mod.ts"; + +export class Traceable extends TraceableImpl { + static from(t: T) { + return new Traceable(t, new TraceableLogger()); + } + + static withFunctionTrace( + f: F, + ): ITraceableMapper> { + return (t) => [t.item, f.name]; + } + + static withClassTrace( + c: C, + ): ITraceableMapper> { + return (t) => [t.item, c.constructor.name]; + } + + static promiseify( + mapper: ITraceableMapper, + ): ITraceableMapper, TraceableLogger, Promise> { + return (traceablePromise) => + traceablePromise.flatMapAsync(async (t) => + t.move(await t.item).map(mapper) + ).item; + } +} diff --git a/utils/either.ts b/utils/either.ts deleted file mode 100644 index 10e4f43..0000000 --- a/utils/either.ts +++ /dev/null @@ -1,48 +0,0 @@ -export interface IEither { - ok?: T; - err?: E; - mapBoth: ( - errBranch: (e: E) => Ee, - okBranch: (o: T) => Tt, - ) => IEither; - mapRight: (mapper: (t: T) => Tt) => Either; - mapLeft: (mapper: (e: E) => Ee) => Either; - flatMap: ( - mapper: (e: T) => Either, - ) => Either; -} - -export class Either implements IEither { - private constructor(readonly err?: E, readonly ok?: T) {} - - public mapBoth( - errBranch: (e: E) => Ee, - okBranch: (t: T) => Tt, - ): Either { - if (this.err) return Either.left(errBranch(this.err)); - return Either.right(okBranch(this.ok!)); - } - - public flatMap(mapper: (t: T) => Either) { - if (this.ok) return mapper(this.ok); - return this; - } - - public mapRight(mapper: (t: T) => Tt): Either { - if (this.ok) return Either.right(mapper(this.ok)); - return Either.left(this.err!); - } - - public mapLeft(mapper: (e: E) => Ee): Either { - if (this.err) return Either.left(mapper(this.err)); - return Either.right(this.ok!); - } - - static left(e: E) { - return new Either(e); - } - - static right(t: T) { - return new Either(undefined, t); - } -} diff --git a/utils/env.ts b/utils/env.ts deleted file mode 100644 index 31b7ccf..0000000 --- a/utils/env.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const getRequiredEnv = (name: string): string => { - const value = Deno.env.get(name); - if (!value) throw new Error(`environment variable "${name}" is required D:`); - return value; -}; diff --git a/utils/isObject.ts b/utils/isObject.ts deleted file mode 100644 index 73f7f80..0000000 --- a/utils/isObject.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const isObject = (o: unknown): o is object => - typeof o === "object" && !Array.isArray(o) && !!o; diff --git a/utils/mod.ts b/utils/mod.ts deleted file mode 100644 index d8cb526..0000000 --- a/utils/mod.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from "./isObject.ts"; -export * from "./trace.ts"; -export * from "./either.ts"; -export * from "./env.ts"; -export * from "./run.ts"; -export * from "./secret.ts"; -export * from "./validate_identifier.ts"; -export * from "./prepend.ts"; diff --git a/utils/prepend.ts b/utils/prepend.ts deleted file mode 100644 index 9b77aff..0000000 --- a/utils/prepend.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const prependWith = (arr: string[], prep: string) => - Array(arr.length * 2).fill(0) - .map((_, i) => i % 2 === 0) - .map((isPrep, i) => isPrep ? prep : arr[i]); diff --git a/utils/run.ts b/utils/run.ts deleted file mode 100644 index 9093863..0000000 --- a/utils/run.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Either, type Traceable } from "./mod.ts"; - -export class ProcessError extends Error {} -export const getStdout = async ( - { item: cmd, logger: _logger }: Traceable, - options: Deno.CommandOptions = {}, -): Promise> => { - const logger = _logger.addTracer(() => "[getStdout]"); - - logger.log(`:> im gonna run this command!`, cmd); - const [exec, ...args] = (typeof cmd === "string") ? cmd.split(" ") : cmd; - const command = new Deno.Command(exec, { - args, - stdout: "piped", - stderr: "piped", - ...options, - }); - - try { - const { code, stdout, stderr } = await command.output(); - const stdoutText = new TextDecoder().decode(stdout); - const stderrText = new TextDecoder().decode(stderr); - - if (code !== 0) { - logger.error(`i weceived an exit code of ${code} i wanna zeroooo :<`); - return Either.left( - new ProcessError(`command failed\n${stderrText}`), - ); - } - - logger.log("yay! i got code 0 :3", cmd); - return Either.right(stdoutText); - } catch (e) { - logger.error(`o.o wat`, e); - if (e instanceof Error) { - return Either.left(e); - } - throw new Error("unknown error " + e); - } -}; diff --git a/utils/trace.ts b/utils/trace.ts deleted file mode 100644 index 1a5e51d..0000000 --- a/utils/trace.ts +++ /dev/null @@ -1,130 +0,0 @@ -export interface Logger { - log: (...args: unknown[]) => void; - debug: (...args: unknown[]) => void; - warn: (...args: unknown[]) => void; - error: (...args: unknown[]) => void; -} - -type Supplier = () => T; -type TraceSupplier = Supplier; -export interface ITraceableLogger> - extends Logger { - addTracer: (traceSupplier: TraceSupplier) => L; -} - -export type ITraceableTuple = [T, TraceSupplier]; -export type ITraceableMapper, U> = ( - t: ITraceable, -) => U; -export interface ITraceable> { - item: T; - logger: L; - - map: (mapper: ITraceableMapper) => ITraceable; - bimap: ( - mapper: ITraceableMapper>, - ) => ITraceable; - peek: (peek: ITraceableMapper) => ITraceable; - flatMap: ( - mapper: ITraceableMapper>, - ) => ITraceable; - flatMapAsync( - mapper: ITraceableMapper>>, - ): ITraceable, L>; - move(t: Tt): ITraceable; -} - -export class TraceableLogger implements ITraceableLogger { - private readonly logger: Logger = console; - constructor( - private readonly traces = [() => `[${new Date().toISOString()}]`], - ) { - } - - public debug(...args: unknown[]) { - this.logger.debug("[DEBUG]", ...this.getPrefix(), args); - } - - public log(...args: unknown[]) { - this.logger.log("[INFO]", ...this.getPrefix(), args); - } - - public warn(...args: unknown[]) { - this.logger.warn("[WARN]", ...this.getPrefix(), args); - } - - public error(...args: unknown[]) { - this.logger.error("[ERROR]", ...this.getPrefix(), args); - } - - public addTracer(traceSupplier: TraceSupplier) { - return new TraceableLogger(this.traces.concat(traceSupplier)); - } - - private getPrefix() { - return this.traces.map((tracer) => tracer()); - } -} - -class TraceableImpl< - T, - L extends ITraceableLogger, -> implements ITraceable { - protected constructor(readonly item: T, readonly logger: L) {} - - public map(mapper: ITraceableMapper) { - const result = mapper(this); - return new TraceableImpl(result, this.logger); - } - - public flatMap( - mapper: ITraceableMapper>, - ): ITraceable { - return mapper(this); - } - - public flatMapAsync( - mapper: ITraceableMapper>>, - ): ITraceable, L> { - return new TraceableImpl( - mapper(this).then(({ item }) => item), - this.logger, - ); - } - - public peek(peek: ITraceableMapper) { - peek(this); - return this; - } - - public move(t: Tt) { - return this.map(() => t); - } - - public bimap(mapper: ITraceableMapper>) { - const [item, trace] = mapper(this); - return new TraceableImpl(item, this.logger.addTracer(trace)); - } - - static promiseify, U>( - mapper: ITraceableMapper, - ): ITraceableMapper, L, Promise> { - return (traceablePromise) => - traceablePromise.flatMapAsync(async (t) => { - const item = await t.item; - return t.map(() => item).map(mapper); - }).item; - } -} - -export class Traceable extends TraceableImpl { - static withClassTrace( - c: C, - ): ITraceableMapper> { - return (t) => [t.item, () => c.constructor.name]; - } - - static from(t: T) { - return new Traceable(t, new TraceableLogger()); - } -} diff --git a/utils/validate_identifier.ts b/utils/validate_identifier.ts deleted file mode 100644 index ec8b77b..0000000 --- a/utils/validate_identifier.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Either } from "./mod.ts"; - -export const validateIdentifier = (token: string) => { - return (/^[a-zA-Z0-9_\-:. \/]+$/).test(token) && !token.includes(".."); -}; - -// ensure {@param obj} is a Record with stuff that won't -// have the potential for shell injection, just to be super safe. -export const validateExecutionEntries = ( - obj: Record, -): Either, Record> => { - const invalidEntries = Object.entries(obj).filter((e) => - !e.every((x) => typeof x === "string" && validateIdentifier(x)) - ); - if (invalidEntries.length > 0) return Either.left(invalidEntries); - return Either.right(> obj); -}; -- cgit v1.2.3-70-g09d2