1+ import { registerAs } from "@nestjs/config" ;
12import bytes from "bytes" ;
23import { createHash , randomBytes } from "crypto" ;
4+ import * as dotenv from "dotenv" ;
5+ import { existsSync } from "fs" ;
36import { readFileSync } from "fs-extra" ;
47import { toLower } from "lodash" ;
8+ import { join } from "path" ;
9+ import { parse as parseYaml } from "yaml" ;
510import packageJson from "../package.json" ;
611import globals from "./globals" ;
712
13+ dotenv . config ( ) ;
14+
15+ let yamlConfigurationCache : Record < string , unknown > | null | undefined ;
16+
17+ function getConfigVolumePath ( ) : string {
18+ return process . env . VOLUMES_CONFIG ?. replace ( / \/ $ / , "" ) || "/config" ;
19+ }
20+
21+ function getYamlConfiguration ( ) : Record < string , unknown > | null {
22+ if ( yamlConfigurationCache !== undefined ) {
23+ return yamlConfigurationCache ;
24+ }
25+
26+ const configVolumePath = getConfigVolumePath ( ) ;
27+ const candidates = [ "config.yaml" , "config.yml" ] ;
28+
29+ for ( const candidate of candidates ) {
30+ const yamlPath = join ( configVolumePath , candidate ) ;
31+ if ( ! existsSync ( yamlPath ) ) {
32+ continue ;
33+ }
34+
35+ try {
36+ const parsed = parseYaml ( readFileSync ( yamlPath , "utf-8" ) ) ;
37+ if ( parsed && typeof parsed === "object" && ! Array . isArray ( parsed ) ) {
38+ yamlConfigurationCache = parsed as Record < string , unknown > ;
39+ return yamlConfigurationCache ;
40+ }
41+
42+ throw new Error ( "Configuration root must be a YAML mapping/object." ) ;
43+ } catch ( error ) {
44+ throw new Error (
45+ `Failed to parse YAML configuration at \"${ yamlPath } \": ${ error instanceof Error ? error . message : String ( error ) } ` ,
46+ ) ;
47+ }
48+ }
49+
50+ yamlConfigurationCache = null ;
51+ return yamlConfigurationCache ;
52+ }
53+
54+ function getYamlValueByPath (
55+ source : Record < string , unknown > ,
56+ pathSegments : string [ ] ,
57+ ) : unknown {
58+ let current : unknown = source ;
59+
60+ for ( const segment of pathSegments ) {
61+ if ( ! current || typeof current !== "object" || Array . isArray ( current ) ) {
62+ return undefined ;
63+ }
64+
65+ const record = current as Record < string , unknown > ;
66+ const matchedKey = Object . keys ( record ) . find (
67+ ( key ) => key . toLowerCase ( ) === segment . toLowerCase ( ) ,
68+ ) ;
69+
70+ if ( ! matchedKey ) {
71+ return undefined ;
72+ }
73+
74+ current = record [ matchedKey ] ;
75+ }
76+
77+ return current ;
78+ }
79+
80+ function toEnvironmentString ( value : unknown ) : string | undefined {
81+ if ( value === undefined || value === null ) {
82+ return undefined ;
83+ }
84+ if ( Array . isArray ( value ) ) {
85+ return value . map ( String ) . join ( "," ) ;
86+ }
87+ if (
88+ typeof value === "string" ||
89+ typeof value === "number" ||
90+ typeof value === "boolean"
91+ ) {
92+ return String ( value ) ;
93+ }
94+ return JSON . stringify ( value ) ;
95+ }
96+
97+ function resolveYamlEnvFallback ( name : string ) : string | undefined {
98+ const yamlConfiguration = getYamlConfiguration ( ) ;
99+ if ( ! yamlConfiguration ) {
100+ return undefined ;
101+ }
102+
103+ const directValue = getYamlValueByPath ( yamlConfiguration , [ name ] ) ;
104+ if ( directValue !== undefined ) {
105+ return toEnvironmentString ( directValue ) ;
106+ }
107+
108+ const nestedValue = getYamlValueByPath ( yamlConfiguration , name . split ( "_" ) ) ;
109+ return toEnvironmentString ( nestedValue ) ;
110+ }
111+
8112/**
9113 * Resolves an environment variable with Docker Secrets support.
10114 * If `<name>_FILE` is set, reads the file at that path and returns its
@@ -23,7 +127,12 @@ function resolveEnv(name: string): string | undefined {
23127 ) ;
24128 }
25129 }
26- return process . env [ name ] ;
130+
131+ if ( process . env [ name ] !== undefined ) {
132+ return process . env [ name ] ;
133+ }
134+
135+ return resolveYamlEnvFallback ( name ) ;
27136}
28137
29138function parseBooleanEnvVariable (
@@ -324,6 +433,15 @@ const configuration = {
324433 } as const ,
325434} as const ;
326435
436+ export const CONFIG_NAMESPACE = "gamevault" ;
437+
438+ export const gamevaultConfiguration = registerAs (
439+ CONFIG_NAMESPACE ,
440+ ( ) => configuration ,
441+ ) ;
442+
443+ export type AppConfiguration = typeof configuration ;
444+
327445export function getCensoredConfiguration ( ) {
328446 const censoredConfig = JSON . parse (
329447 JSON . stringify ( configuration , ( _k , v ) => ( v === undefined ? null : v ) ) ,
0 commit comments