diff --git a/src/utils/environment.ts b/src/utils/environment.ts new file mode 100644 index 0000000..2f4e68c --- /dev/null +++ b/src/utils/environment.ts @@ -0,0 +1,134 @@ +import { asString } from "@/utils/string-utils"; +import { EOL } from "node:os"; + +/** + * An object containing environment variables as key-value pairs. + */ +export const ENVIRONMENT: Record = process.env; + +/** + * The Windows-style line break character sequence. + */ +export const WINDOWS_NEWLINE = "\r\n"; + +/** + * The Unix-style line break character sequence. + */ +export const UNIX_NEWLINE = "\n"; + +/** + * The default line break character sequence based on the operating system. + */ +export const DEFAULT_NEWLINE = EOL; + +/** + * Retrieves the environment variable with the specified `name`. + * + * @param name - The name of the environment variable to retrieve. + * @param env - An optional set of the environment variables to search within. Defaults to `process.env`. + * + * @returns The value of the specified environment variable, if any; otherwise, `undefined`. + */ +export function getEnvironmentVariable(name: string, env?: Record): string { + env ||= ENVIRONMENT; + + const variable = env[name]; + return variable === undefined ? undefined : asString(variable); +} + +/** + * Returns an iterable that yields all environment variables as name/value key-value pairs. + * + * @param env - An optional set of the environment variables to search within. Defaults to `process.env`. + * + * @returns An iterable that yields all environment variables as name/value key-value pairs. + */ +export function* getAllEnvironmentVariables(env?: Record): Iterable<[string, string]> { + env ||= ENVIRONMENT; + + for (const [name, variable] of Object.entries(env)) { + if (variable === undefined) { + continue; + } + + yield [name, asString(variable)]; + } +} + +/** + * Updates the value of an environment variable with the specified name. + * + * @param name - The name of the environment variable to update. + * @param value - The new value for the environment variable. + * @param env - An optional set of the environment variables to update. Defaults to `process.env`. + */ +export function setEnvironmentVariable(name: string, value: unknown, env?: Record): void { + env ||= ENVIRONMENT; + + if (value === undefined) { + delete env[name]; + } else { + env[name] = asString(value); + } +} + +/** + * Determines whether the current environment is in debug mode. + * + * @param env - An optional set of the environment variables to check. Defaults to `process.env`. + * + * @returns `true` if the environment is in debug mode; otherwise, `false`. + */ +export function isDebug(env?: Record): boolean { + // Why in the world is this "1" instead of "true"? + // https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + return getEnvironmentVariable("RUNNER_DEBUG", env) === "1"; +} + +/** + * Determines whether the current environment is running on GitHub Actions. + * + * @param env - An optional set of the environment variables to check. Defaults to `process.env`. + * + * @returns `true` if the current environment is running on GitHub Actions; otherwise, `false`. + */ +export function isGitHubAction(env?: Record): boolean { + // https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + return getEnvironmentVariable("GITHUB_ACTIONS", env) === "true"; +} + +/** + * Determines whether the specified platform is Windows. + * + * @param platformName - An optional string that represents the platform to check. If not provided, the current platform will be used as the default. + * + * @returns `true` if the specified platform is Windows; otherwise, `false`. + */ +export function isWindows(platformName?: string): boolean { + platformName ??= process.platform; + return (platformName as typeof process.platform) === "win32"; +} + +/** + * Determines whether the current platform is macOS. + * + * @param platformName - An optional string that represents the platform to check. If not provided, the current platform will be used as the default. + * + * @returns `true` if the current platform is macOS; otherwise, `false`. + */ +export function isMacOs(platformName?: string): boolean { + platformName ??= process.platform; + return (platformName as typeof process.platform) === "darwin"; +} + +/** + * Determines whether the current platform is Linux. + * + * @param platformName - An optional string that represents the platform to check. If not provided, the current platform will be used as the default. + * + * @returns `true` if the current platform is Linux; otherwise, `false`. + */ +export function isLinux(platformName?: string): boolean { + platformName ??= process.platform; + return (platformName as typeof process.platform) === "linux"; +} diff --git a/tests/unit/utils/environment.spec.ts b/tests/unit/utils/environment.spec.ts new file mode 100644 index 0000000..11ba84e --- /dev/null +++ b/tests/unit/utils/environment.spec.ts @@ -0,0 +1,224 @@ +import { + getEnvironmentVariable, + getAllEnvironmentVariables, + setEnvironmentVariable, + isDebug, + isGitHubAction, + isWindows, + isMacOs, + isLinux, +} from "@/utils/environment"; + +const OLD_ENV = { ...process.env }; + +const OLD_PLATFORM = Object.getOwnPropertyDescriptor(process, "platform"); + +beforeEach(() => { + jest.resetModules(); +}); + +afterEach(() => { + Object.keys(process.env).forEach(key => delete process.env[key]); + Object.assign(process.env, OLD_ENV); + Object.defineProperty(process, "platform", OLD_PLATFORM); +}); + +describe("getEnvironmentVariable", () => { + test("gets environment variable", () => { + const env = { TEST_VAR: "test" }; + + const testVar = getEnvironmentVariable("TEST_VAR", env); + + expect(testVar).toBe("test"); + }); + + test("always returns a string for existing values", () => { + const env = { TEST_VAR: 42 as unknown as string }; + + const testVar = getEnvironmentVariable("TEST_VAR", env); + + expect(testVar).toBe("42"); + }); + + test("always returns undefined for undefined values", () => { + const env = {}; + + const testVar = getEnvironmentVariable("TEST_VAR", env); + + expect(testVar).toBeUndefined(); + }); + + test("uses process.env by default", () => { + process.env.TEST_VAR = "test"; + + const testVar = getEnvironmentVariable("TEST_VAR"); + + expect(testVar).toBe("test"); + }); +}); + +describe("getAllEnvironmentVariables", () => { + test("returns all environment variables as name/value key-value pairs", () => { + const env = { + VAR1: "value1", + VAR2: "value2", + VAR3: "value3", + }; + + const result = Array.from(getAllEnvironmentVariables(env)); + + expect(result).toEqual([ + ["VAR1", "value1"], + ["VAR2", "value2"], + ["VAR3", "value3"], + ]); + }); + + test("skips variables with undefined value", () => { + const env = { + VAR1: "value1", + VAR2: undefined, + VAR3: "value3", + }; + + const result = Array.from(getAllEnvironmentVariables(env)); + + expect(result).toEqual([ + ["VAR1", "value1"], + ["VAR3", "value3"], + ]); + }); + + test("always returns strings for existing values", () => { + const env = { TEST_VAR: 42 as unknown as string }; + + const result = Array.from(getAllEnvironmentVariables(env)); + + expect(result).toEqual([ + ["TEST_VAR", "42"], + ]); + }); + + test("uses process.env by default", () => { + process.env.TEST_VAR = "testValue"; + + const result = Array.from(getAllEnvironmentVariables()).find(([name]) => name === "TEST_VAR"); + + expect(result).toEqual(["TEST_VAR", "testValue"]); + }); +}); + +describe("setEnvironmentVariable", () => { + test("sets environment variable", () => { + const env = {}; + + setEnvironmentVariable("TEST_VAR", "test", env); + + expect(env["TEST_VAR"]).toBe("test"); + }); + + test("always converts values to strings", () => { + const env = {}; + + setEnvironmentVariable("TEST_VAR", 42, env); + + expect(env["TEST_VAR"]).toBe("42"); + }); + + test("uses process.env by default", () => { + setEnvironmentVariable("TEST_VAR", "test"); + + expect(process.env.TEST_VAR).toBe("test"); + }); +}); + +describe("isDebug", () => { + test("returns true when RUNNER_DEBUG is 1", () => { + const env = { RUNNER_DEBUG: "1" }; + + expect(isDebug(env)).toBe(true); + }); + + test("returns false when RUNNER_DEBUG is not 1", () => { + const env = { RUNNER_DEBUG: "true" }; + + expect(isDebug(env)).toBe(false); + }); + + test("uses process.env by default", () => { + process.env.RUNNER_DEBUG = "1"; + + expect(isDebug()).toBe(true); + }); +}); + +describe("isGitHubAction", () => { + test("returns true when GITHUB_ACTIONS is 'true'", () => { + const env = { GITHUB_ACTIONS: "true" }; + + expect(isGitHubAction(env)).toBe(true); + }); + + test("returns false when GITHUB_ACTIONS is not 'true'", () => { + const env = {}; + + expect(isGitHubAction(env)).toBe(false); + }); + + test("uses process.env by default", () => { + process.env.GITHUB_ACTIONS = "true"; + + expect(isGitHubAction()).toBe(true); + }); +}); + +describe("isWindows", () => { + test("returns true when platform is Windows", () => { + expect(isWindows("win32")).toBe(true); + }); + + test("returns false when platform is not Windows", () => { + expect(isWindows("darwin")).toBe(false); + expect(isWindows("linux")).toBe(false); + }); + + test("uses process.platform by default", () => { + Object.defineProperty(process, "platform", { value: "win32" }); + + expect(isWindows()).toBe(true); + }); +}); + +describe("isMacOS", () => { + test("returns true when platform is macOS", () => { + expect(isMacOs("darwin")).toBe(true); + }); + + test("returns false when platform is not macOS", () => { + expect(isMacOs("win32")).toBe(false); + expect(isMacOs("linux")).toBe(false); + }); + + test("uses process.platform by default", () => { + Object.defineProperty(process, "platform", { value: "darwin" }); + + expect(isMacOs()).toBe(true); + }); +}); + +describe("isLinux", () => { + test("returns true when platform is Linux", () => { + expect(isLinux("linux")).toBe(true); + }); + + test("returns false when platform is not Linux", () => { + expect(isLinux("win32")).toBe(false); + expect(isLinux("darwin")).toBe(false); + }); + + test("uses process.platform by default", () => { + Object.defineProperty(process, "platform", { value: "linux" }); + + expect(isLinux()).toBe(true); + }); +});