| 33 | * Matched keys that have `null` | `undefined` values are treated as not found. |
| 34 | */ |
| 35 | export async function injectVariables<C extends InjectableConfigType>( |
| 36 | config: C, |
| 37 | variables: Record<string, undefined | null | string | Record<string, undefined | null | string>>, |
| 38 | propNotFoundValue?: any, |
| 39 | ) { |
| 40 | const isObject = typeof config === "object" |
| 41 | let configString: string = isObject ? JSON.stringify(config) : config |
| 42 | |
| 43 | for (const [key, value] of Object.entries(variables)) { |
| 44 | if (value == null) continue |
| 45 | |
| 46 | if (typeof value === "string") { |
| 47 | // Normalize paths to forward slashes for cross-platform compatibility |
| 48 | configString = configString.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value.toPosix()) |
| 49 | } else { |
| 50 | // Handle nested variables (e.g., ${env:VAR_NAME}) |
| 51 | configString = configString.replace(new RegExp(`\\$\\{${key}:([\\w]+)\\}`, "g"), (match, name) => { |
| 52 | const nestedValue = value[name] |
| 53 | |
| 54 | if (nestedValue == null) { |
| 55 | console.warn(`[injectVariables] variable "${name}" referenced but not found in "${key}"`) |
| 56 | return propNotFoundValue ?? match |
| 57 | } |
| 58 | |
| 59 | // Normalize paths for string values |
| 60 | return typeof nestedValue === "string" ? nestedValue.toPosix() : nestedValue |
| 61 | }) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | return (isObject ? JSON.parse(configString) : configString) as C extends string ? string : C |
| 66 | } |