Files
kuber/lib/context.ts
T

73 lines
1.9 KiB
TypeScript

import { AsyncLocalStorage } from "node:async_hooks";
import type { ComposeSpecification } from "../schema/docker.d";
import type { KuberHookContext } from "../types";
import { hookContext, loadConfig, type ResolvedKuberConfig } from "./config";
import { readCompose, resolveComposeFile } from "./yaml";
type AppContext = {
project: string;
cwd: string;
config: ResolvedKuberConfig;
path(): Promise<string | undefined>;
hookContext(): Promise<KuberHookContext>;
compose(): Promise<ComposeSpecification>;
};
const appContext = new AsyncLocalStorage<AppContext>();
export async function provideContext<T>(
fn: () => T,
configPath?: string,
): Promise<Awaited<T>> {
const cwd = process.cwd();
const config = await loadConfig(cwd, configPath);
const path = createCachedGetter("composeFile", () =>
config.composeFile
? Promise.resolve(config.composeFile)
: resolveComposeFile(cwd),
);
const getHookContext = createCachedGetter("hookContext", async () => {
const composeFile = await path();
if (!composeFile) throw new Error("Compose file cannot be found");
return hookContext(config, cwd, composeFile);
});
const compose = createCachedGetter("compose", async () => {
const context = await getHookContext();
const value = await readCompose(context.composeFile);
await config.compose?.(value, context);
return value;
});
return await appContext.run(
{
project: config.project,
cwd,
config,
path,
hookContext: getHookContext,
compose,
},
fn,
);
}
export function ctx() {
const store = appContext.getStore();
if (!store) throw new Error("Not in context!");
return store;
}
export function createCachedGetter<T>(
_key: string,
fn: () => Promise<T>,
): () => Promise<T> {
let cached: Promise<T> | undefined;
return () => {
cached ??= fn().catch((error) => {
cached = undefined;
throw error;
});
return cached;
};
}