dotenv-mono
Version:
This package permit to have a centralized dotenv on a monorepo. It also includes some extra features such as manipulation and saving of changes to the dotenv file, a default centralized file, and a file loader with ordering and priorities.
348 lines • 18.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_cli_1 = require("./node-cli");
const index_1 = require("./index");
const mock_fs_1 = __importDefault(require("mock-fs"));
describe("Run CLI", () => {
const originalEnv = process.env;
beforeEach(() => {
process.env = Object.assign(Object.assign({}, originalEnv), { NODE_ENV: "test" });
process.argv = ["node", "script.js"]; // Reset argv to default
(0, mock_fs_1.default)({
"/root": {
".env": "TEST_ROOT_ENV=1",
"apps": {}, // Create the apps directory structure
},
});
jest.spyOn(process, "cwd").mockReturnValue("/root/apps");
});
afterEach(() => {
// Restore original process.env and argv
process.env = originalEnv;
process.argv = ["node", "script.js"];
jest.resetModules();
mock_fs_1.default.restore();
jest.restoreAllMocks();
});
it("should expose a function", () => {
expect(node_cli_1.runNodeCli).toBeDefined();
});
it("should not throw errors", () => {
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
expect(() => (0, node_cli_1.runNodeCli)(index_1.config)).not.toThrow();
});
it("should return the expected output", () => {
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv instanceof index_1.Dotenv).toBeTruthy();
expect(dotenv.env).not.toBeEmptyObject();
const output = (0, node_cli_1.runNodeCli)(index_1.config);
expect(output).toHaveProperty("parsed");
expect(output.parsed).not.toBeEmptyObject();
});
it("should return the expected output using environmental options", () => {
process.env.DOTENV_CONFIG_DEBUG = "true";
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv instanceof index_1.Dotenv).toBeTruthy();
expect(dotenv.env).not.toBeEmptyObject();
});
it("should return the expected output using argv options", () => {
process.argv = ["node", "script.js", "dotenv_config_debug=true"];
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv instanceof index_1.Dotenv).toBeTruthy();
expect(dotenv.env).not.toBeEmptyObject();
});
it("should handle multiple environmental options", () => {
process.env.DOTENV_CONFIG_DEBUG = "true";
process.env.DOTENV_CONFIG_OVERRIDE = "true";
process.env.DOTENV_CONFIG_EXPAND = "false";
process.env.DOTENV_CONFIG_DEPTH = "2";
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv instanceof index_1.Dotenv).toBeTruthy();
expect(dotenv.debug).toBe(true);
expect(dotenv.override).toBe(true);
expect(dotenv.expand).toBe(false);
expect(dotenv.depth).toBe(2);
});
it("should handle multiple argv options", () => {
process.argv = [
"node",
"script.js",
"dotenv_config_debug=true",
"dotenv_config_override=false",
"dotenv_config_depth=3",
];
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv instanceof index_1.Dotenv).toBeTruthy();
expect(dotenv.debug).toBe(true);
expect(dotenv.override).toBe(false);
expect(dotenv.depth).toBe(3);
});
it("should prioritize argv options over environmental options", () => {
// Set env vars
process.env.DOTENV_CONFIG_DEBUG = "false";
process.env.DOTENV_CONFIG_DEPTH = "5";
// Set argv that should override env vars
process.argv = ["node", "script.js", "dotenv_config_debug=true", "dotenv_config_depth=2"];
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.debug).toBe(true); // argv should win
expect(dotenv.depth).toBe(2); // argv should win
});
it("should handle invalid argv format gracefully", () => {
process.argv = [
"node",
"script.js",
"invalid_option=value",
"dotenv_config_=empty_option",
"dotenv_config_debug=true",
];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.debug).toBe(true); // Valid option should still work
});
});
describe("Parse Option", () => {
it("should expose a function", () => {
expect(node_cli_1.parseOption).toBeDefined();
});
it("should return expected output", () => {
expect(node_cli_1.parseOption).toBeDefined();
expect((0, node_cli_1.parseOption)(undefined, node_cli_1.OptionType.number)).toBeUndefined();
expect((0, node_cli_1.parseOption)("1", node_cli_1.OptionType.number)).toEqual(1);
expect((0, node_cli_1.parseOption)("true", node_cli_1.OptionType.boolean)).toBeTrue();
expect((0, node_cli_1.parseOption)("false", node_cli_1.OptionType.boolean)).toBeFalse();
expect((0, node_cli_1.parseOption)("message", node_cli_1.OptionType.string)).toEqual("message");
expect((0, node_cli_1.parseOption)('{"value": 1}', node_cli_1.OptionType.object)).toEqual({ "value": 1 });
expect((0, node_cli_1.parseOption)("[1, 2]", node_cli_1.OptionType.array)).toEqual([1, 2]);
expect((0, node_cli_1.parseOption)('{"value": 1, "empty": null}', node_cli_1.OptionType.mapOfNumbers)).toEqual({
"value": 1,
"empty": null,
});
// Wrong JSON data
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)("1", node_cli_1.OptionType.object)).toBeUndefined();
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('{"value": "string"}', node_cli_1.OptionType.mapOfNumbers)).toBeUndefined();
// Malformed JSON parsing
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('{"malformed": 1]]', node_cli_1.OptionType.object)).toBeUndefined();
});
it("should handle null input for parseOption", () => {
expect((0, node_cli_1.parseOption)(undefined, node_cli_1.OptionType.string)).toBeUndefined();
});
it("should handle number parsing edge cases", () => {
expect((0, node_cli_1.parseOption)("0", node_cli_1.OptionType.number)).toBe(0);
expect((0, node_cli_1.parseOption)("-123", node_cli_1.OptionType.number)).toBe(-123);
expect((0, node_cli_1.parseOption)("123.456", node_cli_1.OptionType.number)).toBe(123.456);
expect((0, node_cli_1.parseOption)("Infinity", node_cli_1.OptionType.number)).toBe(Infinity);
expect((0, node_cli_1.parseOption)("NaN", node_cli_1.OptionType.number)).toBeNaN();
expect((0, node_cli_1.parseOption)("abc", node_cli_1.OptionType.number)).toBeNaN();
});
it("should handle boolean parsing edge cases", () => {
expect((0, node_cli_1.parseOption)("TRUE", node_cli_1.OptionType.boolean)).toBe(false); // Only "true" should be true
expect((0, node_cli_1.parseOption)("false", node_cli_1.OptionType.boolean)).toBe(false);
expect((0, node_cli_1.parseOption)("1", node_cli_1.OptionType.boolean)).toBe(false);
expect((0, node_cli_1.parseOption)("0", node_cli_1.OptionType.boolean)).toBe(false);
expect((0, node_cli_1.parseOption)("", node_cli_1.OptionType.boolean)).toBe(false);
});
it("should handle array parsing", () => {
expect((0, node_cli_1.parseOption)("[]", node_cli_1.OptionType.array)).toEqual([]);
expect((0, node_cli_1.parseOption)('["item1", "item2"]', node_cli_1.OptionType.array)).toEqual(["item1", "item2"]);
expect((0, node_cli_1.parseOption)("[1, 2, 3]", node_cli_1.OptionType.array)).toEqual([1, 2, 3]);
// Invalid array JSON
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)("[invalid]", node_cli_1.OptionType.array)).toBeUndefined();
});
it("should handle mapOfNumbers validation", () => {
expect((0, node_cli_1.parseOption)("{}", node_cli_1.OptionType.mapOfNumbers)).toEqual({});
expect((0, node_cli_1.parseOption)('{"a": 1, "b": 2}', node_cli_1.OptionType.mapOfNumbers)).toEqual({ "a": 1, "b": 2 });
expect((0, node_cli_1.parseOption)('{"a": null, "b": undefined}', node_cli_1.OptionType.mapOfNumbers)).toEqual({
"a": null,
});
// Should reject non-number values
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('{"a": "string"}', node_cli_1.OptionType.mapOfNumbers)).toBeUndefined();
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('{"a": true}', node_cli_1.OptionType.mapOfNumbers)).toBeUndefined();
});
it("should handle string parsing with special characters", () => {
expect((0, node_cli_1.parseOption)("hello world", node_cli_1.OptionType.string)).toBe("hello world");
expect((0, node_cli_1.parseOption)("special!@#$%^&*()", node_cli_1.OptionType.string)).toBe("special!@#$%^&*()");
expect((0, node_cli_1.parseOption)("unicode: 你好", node_cli_1.OptionType.string)).toBe("unicode: 你好");
expect((0, node_cli_1.parseOption)("", node_cli_1.OptionType.string)).toBe("");
});
it("should handle null value as parameter", () => {
// Test null handling which is covered in the condition: option === null
expect((0, node_cli_1.parseOption)(null, node_cli_1.OptionType.string)).toBeUndefined();
expect((0, node_cli_1.parseOption)(null, node_cli_1.OptionType.number)).toBeUndefined();
expect((0, node_cli_1.parseOption)(null, node_cli_1.OptionType.boolean)).toBeUndefined();
expect((0, node_cli_1.parseOption)(null, node_cli_1.OptionType.array)).toBeUndefined();
expect((0, node_cli_1.parseOption)(null, node_cli_1.OptionType.object)).toBeUndefined();
expect((0, node_cli_1.parseOption)(null, node_cli_1.OptionType.mapOfNumbers)).toBeUndefined();
});
it("should reject non-array values for array type", () => {
// Test ParseError for non-array values
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('{"key": "value"}', node_cli_1.OptionType.array)).toBeUndefined();
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('"string"', node_cli_1.OptionType.array)).toBeUndefined();
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)("123", node_cli_1.OptionType.array)).toBeUndefined();
});
it("should reject array values for object type", () => {
// Test ParseError for array values when expecting object
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)("[1, 2, 3]", node_cli_1.OptionType.object)).toBeUndefined();
jest.spyOn(console, "error").mockImplementationOnce(() => { });
expect((0, node_cli_1.parseOption)('["item1", "item2"]', node_cli_1.OptionType.object)).toBeUndefined();
});
it("should handle mapOfNumbers preprocessing with undefined values", () => {
// Test the undefined value preprocessing in mapOfNumbers
expect((0, node_cli_1.parseOption)('{"a": 1, "b": undefined, "c": 2}', node_cli_1.OptionType.mapOfNumbers)).toEqual({
"a": 1,
"c": 2,
});
expect((0, node_cli_1.parseOption)('{"first": undefined, "second": 42}', node_cli_1.OptionType.mapOfNumbers)).toEqual({
"second": 42,
});
expect((0, node_cli_1.parseOption)('{"onlyUndefined": undefined}', node_cli_1.OptionType.mapOfNumbers)).toEqual({});
});
// Additional CLI edge cases
it("should handle CLI options with special characters and formatting", () => {
// Mock console.error to suppress expected debug output
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
process.argv = [
"node",
"script.js",
"dotenv_config_path=/path/with spaces/.env",
"dotenv_config_debug=true",
'dotenv_config_priorities={"custom.env": 100}',
];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.debug).toBe(true);
consoleErrorSpy.mockRestore();
});
it("should handle mixed case and malformed CLI arguments", () => {
process.argv = [
"node",
"script.js",
"DOTENV_CONFIG_DEBUG=true", // Wrong case
"dotenv_config_=", // Empty option name
"=value", // No option name
"dotenv_config_depth=abc", // Invalid number
"dotenv_config_override=yes", // Invalid boolean
];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
// Should only parse valid options
expect(dotenv.depth).toBeNaN(); // Invalid number becomes NaN
expect(dotenv.override).toBe(false); // Invalid boolean becomes false
});
it("should handle environment variables with empty values", () => {
process.env.DOTENV_CONFIG_PATH = "";
process.env.DOTENV_CONFIG_DEFAULTS = "";
process.env.DOTENV_CONFIG_EXTENSION = "";
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.path).toBe("");
expect(dotenv.defaults).toBe("");
expect(dotenv.extension).toBe("");
});
it("should handle CLI priority when both env and argv are set", () => {
// Set environment variables
process.env.DOTENV_CONFIG_DEBUG = "false";
process.env.DOTENV_CONFIG_DEPTH = "10";
process.env.DOTENV_CONFIG_OVERRIDE = "true";
// Set argv that should override env vars
process.argv = [
"node",
"script.js",
"dotenv_config_debug=true",
"dotenv_config_depth=5",
// Note: not setting override, so env var should be used
];
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.debug).toBe(true); // argv wins
expect(dotenv.depth).toBe(5); // argv wins
expect(dotenv.override).toBe(true); // env var used since argv not set
});
it("should handle complex JSON in CLI arguments", () => {
// Mock console.error to suppress expected debug output
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
process.argv = [
"node",
"script.js",
'dotenv_config_priorities={"custom.env": 100, "other.env": 50}',
];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.priorities["custom.env"]).toBe(100);
expect(dotenv.priorities["other.env"]).toBe(50);
consoleErrorSpy.mockRestore();
});
it("should handle malformed JSON in CLI arguments gracefully", () => {
jest.spyOn(console, "error").mockImplementationOnce(() => { });
process.argv = ["node", "script.js", 'dotenv_config_priorities={"malformed": json}'];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
// Should use default priorities when JSON parsing fails
expect(dotenv.priorities[".env"]).toBe(1);
});
it("should handle regex special characters in CLI arguments", () => {
// Mock console.error to suppress expected debug output
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
process.argv = [
"node",
"script.js",
"dotenv_config_path=/path/with[brackets]/.env",
"dotenv_config_extension=server.test",
];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.path).toBe("/path/with[brackets]/.env");
expect(dotenv.extension).toBe("server.test");
consoleErrorSpy.mockRestore();
});
it("should handle extremely long CLI arguments", () => {
// Mock console.error to suppress expected debug output
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
const longPath = "/very/long/path/" + "directory/".repeat(100) + ".env";
process.argv = ["node", "script.js", `dotenv_config_path=${longPath}`];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.path).toBe(longPath);
consoleErrorSpy.mockRestore();
});
it("should handle CLI arguments with unicode characters", () => {
// Mock console.error to suppress expected debug output
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
process.argv = [
"node",
"script.js",
"dotenv_config_path=/路径/файл/.env",
"dotenv_config_extension=тест",
];
expect(() => (0, node_cli_1.runNodeCli)(index_1.load)).not.toThrow();
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.path).toBe("/路径/файл/.env");
expect(dotenv.extension).toBe("тест");
consoleErrorSpy.mockRestore();
});
it("should handle duplicate CLI arguments (last one wins)", () => {
process.argv = [
"node",
"script.js",
"dotenv_config_debug=false",
"dotenv_config_depth=1",
"dotenv_config_debug=true", // This should win
"dotenv_config_depth=3", // This should win
];
const dotenv = (0, node_cli_1.runNodeCli)(index_1.load);
expect(dotenv.debug).toBe(true);
expect(dotenv.depth).toBe(3);
});
});
//# sourceMappingURL=node-cli.test.js.map