/*
RFC-1126: lazy .env loader for the resolved site workspace. The kernel CLI loads
dotenv from process.cwd() (the workshop root) only — a workpiece .env is invisible
to pipeline commands unless each command implements its own fallback (the m000075
readWorkpieceEnv pattern). This module generalizes that fallback once: the executor
populates KernelRuntimeContext.workpieceEnv from /.env.
- Does not override process.env — real environment always wins; this map is a fallback only.
- Does not implement a general .env hierarchy (shell profiles, CI secrets) — only the resolved-site .env.
- dotenv.parse is reused (already a kernel dependency) instead of a hand-rolled regex parser.
- Node-only module (node:fs) — kernel-subpath export only, never re-exported from client-reachable barrels.
- RFC-1126: initial implementation — loadWorkpieceEnv + EMPTY_WORKPIECE_ENV.
- RFC-1126: step 1 — shared contracts (drift guard + workpiece env)
Add guardTrackedOverwrite (json/yaml/text lost-key diff, warn-by-default, --strict fail-hard) and loadWorkpieceEnv (dotenv.parse wrapper) to werkstatt-shared kernel subpath; extend KernelRuntimeContext with optional workpieceEnv field.
*/
import { join } from "node:path";
import { parse as dotenvParse } from "dotenv";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
/** Shared empty map for contexts without a resolved site or .env file. */
export const EMPTY_WORKPIECE_ENV: ReadonlyMap = new Map();
/**
* Parse `/.env` into a ReadonlyMap. Returns an empty map when
* the site is undefined or the file is missing/unreadable — never throws.
*/
export async function loadWorkpieceEnv(
siteDirectory: string | undefined,
io: WorkspaceIO = createDefaultIO().io,
): Promise> {
if (!siteDirectory) return EMPTY_WORKPIECE_ENV;
try {
const raw = await io.readFile(join(siteDirectory, ".env"));
return new Map(Object.entries(dotenvParse(raw)));
} catch {
return EMPTY_WORKPIECE_ENV;
}
}