Skip to content

Commit

Permalink
feat: allow oveeridng nested runtime config with env (#1831)
Browse files Browse the repository at this point in the history
* chore(playground): Add GET config endpoint

* fix(runtimeConfig): Replace nested obj value with primitive defined env variables

* chore(playground): Revert changes

* chore: apply automated fixes

* add unit tests

* fix test / restore env

---------

Co-authored-by: Pooya Parsa <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Alexander Lichter <[email protected]>
Co-authored-by: Pooya Parsa <[email protected]>
  • Loading branch information
5 people authored Nov 30, 2023
1 parent 06e1940 commit 1d093d6
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 35 deletions.
2 changes: 1 addition & 1 deletion playground/nitro.config.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export default defineNitroConfig({});
export default defineNitroConfig({});
42 changes: 8 additions & 34 deletions src/runtime/config.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import destr from "destr";
import { snakeCase } from "scule";
import { klona } from "klona";
import { H3Event } from "h3";
import { type EnvOptions, applyEnv } from "./utils.env";
import { appConfig as _inlineAppConfig } from "#internal/nitro/virtual/app-config";
import type { NitroRuntimeConfig } from "nitropack";

// Static runtime config inlined by nitro build
const _inlineRuntimeConfig = process.env.RUNTIME_CONFIG as any;
const ENV_PREFIX = "NITRO_";
const ENV_PREFIX_ALT =
_inlineRuntimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_";
const envOptions: EnvOptions = {
prefix: "NITRO_",
altPrefix:
_inlineRuntimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_",
};

// Runtime config
const _sharedRuntimeConfig = _deepFreeze(
_applyEnv(klona(_inlineRuntimeConfig))
applyEnv(klona(_inlineRuntimeConfig), envOptions)
);
export function useRuntimeConfig<
T extends NitroRuntimeConfig = NitroRuntimeConfig,
Expand All @@ -28,7 +29,7 @@ export function useRuntimeConfig<
}
// Prepare runtime config for event context
const runtimeConfig = klona(_inlineRuntimeConfig) as T;
_applyEnv(runtimeConfig);
applyEnv(runtimeConfig, envOptions);
event.context.nitro.runtimeConfig = runtimeConfig;
return runtimeConfig;
}
Expand All @@ -52,33 +53,6 @@ export function useAppConfig(event?: H3Event) {

// --- Utils ---

function _getEnv(key: string) {
const envKey = snakeCase(key).toUpperCase();
return destr(
process.env[ENV_PREFIX + envKey] ?? process.env[ENV_PREFIX_ALT + envKey]
);
}

function _isObject(input: unknown) {
return typeof input === "object" && !Array.isArray(input);
}

function _applyEnv(obj: object, parentKey = "") {
for (const key in obj) {
const subKey = parentKey ? `${parentKey}_${key}` : key;
const envValue = _getEnv(subKey);
if (_isObject(obj[key])) {
if (_isObject(envValue)) {
obj[key] = { ...obj[key], ...(envValue as any) };
}
_applyEnv(obj[key], subKey);
} else {
obj[key] = envValue ?? obj[key];
}
}
return obj;
}

function _deepFreeze(object: Record<string, any>) {
const propNames = Object.getOwnPropertyNames(object);
for (const name of propNames) {
Expand Down
45 changes: 45 additions & 0 deletions src/runtime/utils.env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import destr from "destr";
import { snakeCase } from "scule";

export type EnvOptions = {
prefix?: string;
altPrefix?: string;
};

export function getEnv(key: string, opts: EnvOptions) {
const envKey = snakeCase(key).toUpperCase();
return destr(
process.env[opts.prefix + envKey] ?? process.env[opts.altPrefix + envKey]
);
}

function _isObject(input: unknown) {
return typeof input === "object" && !Array.isArray(input);
}

export function applyEnv(obj: object, opts: EnvOptions, parentKey = "") {
for (const key in obj) {
const subKey = parentKey ? `${parentKey}_${key}` : key;
const envValue = getEnv(subKey, opts);
if (_isObject(obj[key])) {
// Same as before
if (_isObject(envValue)) {
obj[key] = { ...obj[key], ...(envValue as object) };
applyEnv(obj[key], opts, subKey);
}
// If envValue is undefined
// Then proceed to nested properties
else if (envValue === undefined) {
applyEnv(obj[key], opts, subKey);
}
// If envValue is a primitive other than undefined
// Then set objValue and ignore the nested properties
else {
obj[key] = envValue ?? obj[key];
}
} else {
obj[key] = envValue ?? obj[key];
}
}
return obj;
}
41 changes: 41 additions & 0 deletions test/unit/utils.env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, it, expect } from "vitest";
import { applyEnv } from "../../src/runtime/utils.env";

describe("env utils", () => {
describe("applyEnv", () => {
const tests = [
{
config: { a: 1, b: 2 },
env: { NITRO_A: "123" },
expected: { a: 123, b: 2 },
},
{
config: { feature: { options: { optionA: true, optionB: true } } },
env: { NITRO_FEATURE: false },
expected: { feature: false },
},
{
config: { feature: { options: { optionA: true, optionB: true } } },
env: { NITRO_FEATURE_OPTIONS: false },
expected: { feature: { options: false } },
},
];
for (const test of tests) {
it(`Config: ${JSON.stringify(test.config)} Env: { ${Object.entries(
test.env
)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join(" ")} }`, () => {
for (const key in test.env) {
process.env[key] = test.env[key];
}
expect(applyEnv(test.config, { prefix: "NITRO_" })).toEqual(
test.expected
);
for (const key in test.env) {
delete process.env[key];
}
});
}
});
});

0 comments on commit 1d093d6

Please sign in to comment.