core/quality/eslint/no-direct-environment-access.eslint.rule.ts

core/quality/eslint/no-direct-environment-access.eslint.rule.ts is a file in GovLab Quality. 89 lines of code and 20 definitions.

import type { Rule } from "eslint";
import { packageRootOf } from "#core/resolvers/package.resolver";

const MESSAGE =
    "Packages must not read process.env directly. " +
    "Host-supplied configuration flows through the constructor or factory the package exposes. " +
    "Inject the value: have the consumer read process.env at the call site and pass it into the package. [injection_only_surface]";

const ENV = "env";
const PROCESS = "process";
const PROCESS_MODULES = new Set(["node:process", "process"]);
const TEST_MARKERS = [".test.", ".spec."];

const isEnvironmentPackage = function isEnvironmentPackage(packageRoot: string): boolean {
    return packageRoot.endsWith("/environment");
};

const CONSUMER_BOUNDARIES = ["/bin/", "/cli/", "/entrypoints/", "/examples/", "/tests/"];

const isTestFile = function isTestFile(name: string): boolean {
    return TEST_MARKERS.some((marker) => name.includes(marker));
};

const isConsumerBoundary = function isConsumerBoundary(filename: string): boolean {
    const normalized = filename.replaceAll("\\", "/");
    return (
        CONSUMER_BOUNDARIES.some((boundary) => normalized.includes(boundary)) ||
        isTestFile(normalized.slice(normalized.lastIndexOf("/") + 1))
    );
};

const isEnvKey = function isEnvKey(
    key: Partial<Record<"name" | "value", unknown>> & { type: string },
    computed: boolean,
): boolean {
    if (computed) {
        return key.type === "Literal" && key.value === ENV;
    }
    return key.type === "Identifier" && key.name === ENV;
};

const isProcessEnvAccess = function isProcessEnvAccess(node: Rule.Node): boolean {
    if (node.type !== "MemberExpression") {
        return false;
    }
    if (node.object.type !== "Identifier" || node.object.name !== PROCESS) {
        return false;
    }
    return isEnvKey(node.property, node.computed);
};

const isProcessEnvDestructure = function isProcessEnvDestructure(node: Rule.Node): boolean {
    if (node.type !== "VariableDeclarator" || node.id.type !== "ObjectPattern") {
        return false;
    }
    if (node.init?.type !== "Identifier" || node.init.name !== PROCESS) {
        return false;
    }
    return node.id.properties.some(
        (property) => property.type === "Property" && isEnvKey(property.key, property.computed),
    );
};

const isProcessEnvImport = function isProcessEnvImport(node: Rule.Node): boolean {
    if (node.type !== "ImportSpecifier" || node.parent.type !== "ImportDeclaration") {
        return false;
    }
    const imported = node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
    return imported === ENV && PROCESS_MODULES.has(String(node.parent.source.value));
};

const WATCHED_NODES = ["ImportSpecifier", "MemberExpression", "VariableDeclarator"];

const DETECTORS: readonly ((node: Rule.Node) => boolean)[] = [
    isProcessEnvAccess,
    isProcessEnvDestructure,
    isProcessEnvImport,
];

export default {
    create(context): Rule.RuleListener {
        const packageRoot = packageRootOf(context.filename);
        if (packageRoot === null || isEnvironmentPackage(packageRoot) || isConsumerBoundary(context.filename)) {
            return {};
        }
        const report = (node: Rule.Node): void => {
            if (DETECTORS.some((detect) => detect(node))) {
                context.report({ messageId: "noProcessEnvDirect", node });
            }
        };
        return Object.fromEntries(WATCHED_NODES.map((type) => [type, report]));
    },

    meta: {
        docs: {
            description:
                "Disallow direct process.env access inside workspace packages, whether by member access, destructuring or a named import — config must flow through the constructor or factory the package exposes.",
        },
        messages: { noProcessEnvDirect: MESSAGE },
        schema: [],
        type: "problem",
    },
} satisfies Rule.RuleModule;