singularity-forge/src/env.ts

65 lines
1.9 KiB
TypeScript

import { homedir } from "node:os";
import { join } from "node:path";
import { z } from "zod";
const optionalNonEmptyString = z.string().trim().min(1).optional();
const booleanOneZero = z
.enum(["0", "1"])
.optional()
.transform((value) => value === "1");
export const sfEnvSchema = z.object({
SF_HOME: optionalNonEmptyString,
SF_AGENT_DIR: optionalNonEmptyString,
SF_CODING_AGENT_DIR: optionalNonEmptyString,
SF_STATE_DIR: optionalNonEmptyString,
SF_PROJECT_ID: z
.string()
.trim()
.regex(/^[A-Za-z0-9_-]+$/, {
message:
"SF_PROJECT_ID must contain only letters, numbers, hyphens, and underscores",
})
.optional(),
SF_BIN_PATH: optionalNonEmptyString,
SF_VERSION: optionalNonEmptyString,
SF_WEB_PROJECT_CWD: optionalNonEmptyString,
SF_WEB_DAEMON_MODE: booleanOneZero,
});
export type SfEnv = z.infer<typeof sfEnvSchema>;
/**
* Parse supported SF_* environment variables into a typed object.
*
* Purpose: give runtime code a shared contract for SF-specific environment
* variables instead of scattering ad hoc `process.env` parsing across entry
* points.
*
* Consumer: root CLI/headless modules and web bridge code that need stable SF
* path and mode values.
*/
export function parseSfEnv(env: NodeJS.ProcessEnv = process.env): SfEnv {
return sfEnvSchema.parse(env);
}
/**
* Return typed SF environment values with path defaults applied.
*
* Purpose: centralize default path behavior for SF_HOME and the managed agent
* directory while still validating user-provided overrides.
*
* Consumer: app-paths.ts, cli-logs.ts, headless-query.ts, and future env readers.
*/
export function getSfEnv(env: NodeJS.ProcessEnv = process.env) {
const parsed = parseSfEnv(env);
const sfHome = parsed.SF_HOME ?? join(homedir(), ".sf");
const agentDir =
parsed.SF_AGENT_DIR ?? parsed.SF_CODING_AGENT_DIR ?? join(sfHome, "agent");
return {
...parsed,
sfHome,
agentDir,
};
}