turboenv
Version:
Minimal env var validation for monorepos using Zod
201 lines (199 loc) • 5.77 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
EnvError: () => EnvError,
initEnv: () => initEnv
});
module.exports = __toCommonJS(index_exports);
var import_zod = require("zod");
var EnvError = class extends Error {
constructor(type, variable, message, cause) {
super(message);
this.type = type;
this.variable = variable;
this.cause = cause;
this.name = "EnvError";
}
};
function initEnv(opts) {
const { server, client, strict = true, onError } = opts;
const clientSchema = import_zod.z.object(client);
const serverSchema = import_zod.z.object(server);
const mergedSchema = clientSchema.merge(serverSchema);
const isClient = typeof window !== "undefined";
const getDefaultEnv = () => {
if (isClient) {
try {
return {};
} catch {
return {};
}
} else {
return process.env;
}
};
const buildRuntimeEnv = () => {
const defaultEnv = getDefaultEnv();
if (!opts.runtimeEnv) {
return defaultEnv;
}
const merged = { ...defaultEnv };
const relevantKeys = isClient ? Object.keys(client) : [...Object.keys(client), ...Object.keys(server)];
for (const key of relevantKeys) {
if (key in opts.runtimeEnv) {
const value = opts.runtimeEnv[key];
if (value !== void 0) {
merged[key] = value;
}
}
}
return merged;
};
const rawEnv = buildRuntimeEnv();
for (const key of Object.keys(client)) {
if (!key.startsWith("VITE_")) {
const error = new EnvError(
"invalid",
key,
`\u274C Client environment variable "${key}" must be prefixed with "VITE_"`
);
if (onError) {
onError(error);
continue;
}
throw error;
}
}
let parsed;
try {
if (strict) {
parsed = mergedSchema.parse(rawEnv);
} else {
const result = mergedSchema.safeParse(rawEnv);
if (result.success) {
parsed = result.data;
} else {
parsed = {};
for (const [key, schemaValue] of Object.entries({
...server,
...client
})) {
if (rawEnv[key] !== void 0) {
try {
const zodSchema = schemaValue;
parsed[key] = zodSchema.parse(rawEnv[key]);
} catch (error) {
const envError = new EnvError(
"invalid",
key,
`\u274C Invalid value for environment variable "${key}"`,
error
);
if (onError) {
onError(envError);
} else {
console.warn(envError.message);
}
}
} else {
const envError = new EnvError(
"missing",
key,
`\u274C Missing environment variable "${key}"`
);
if (onError) {
onError(envError);
} else {
console.warn(envError.message);
}
}
}
}
}
} catch (error) {
if (error instanceof import_zod.ZodError) {
const issues = error.issues || [];
const missingVars = [];
const invalidVars = [];
for (const issue of issues) {
const path = issue.path?.join(".") || "unknown";
if (issue.code === "invalid_type") {
const invalidTypeIssue = issue;
if (invalidTypeIssue.received === "undefined" || invalidTypeIssue.received === void 0) {
missingVars.push(path);
} else {
invalidVars.push(
`${path}: expected ${invalidTypeIssue.expected}, received ${invalidTypeIssue.received}`
);
}
} else {
invalidVars.push(`${path}: ${issue.message}`);
}
}
let message = "\u274C Environment validation failed:\n";
if (missingVars.length > 0) {
message += `Missing variables: ${missingVars.join(", ")}
`;
}
if (invalidVars.length > 0) {
message += `Invalid variables: ${invalidVars.join(", ")}
`;
}
const envError = new EnvError("invalid", "", message, error);
if (onError) {
onError(envError);
parsed = {};
} else {
throw envError;
}
} else {
throw error;
}
}
if (isClient) {
for (const key of Object.keys(server)) {
Object.defineProperty(parsed, key, {
get() {
const error = new EnvError(
"client_access",
key,
`\u274C Attempted to access server-only env var "${key}" on the client`
);
if (onError) {
onError(error);
return void 0;
}
throw error;
}
});
}
}
return {
...parsed,
client: parsed,
server: isClient ? void 0 : parsed
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
EnvError,
initEnv
});