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; hookContext(): Promise; compose(): Promise; }; const appContext = new AsyncLocalStorage(); export async function provideContext( fn: () => T, configPath?: string, ): Promise> { 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( _key: string, fn: () => Promise, ): () => Promise { let cached: Promise | undefined; return () => { cached ??= fn().catch((error) => { cached = undefined; throw error; }); return cached; }; }