| | | 1 | | export type ChildEnvironment = { |
| | | 2 | | /** Complete environment for the child process. */ |
| | | 3 | | env: Record<string, string>; |
| | | 4 | | /** Names of inherited variables replaced by a resolved one. */ |
| | | 5 | | overridden: string[]; |
| | | 6 | | }; |
| | | 7 | | |
| | | 8 | | /** |
| | | 9 | | * Composes the environment of an exec child: the inherited environment plus |
| | | 10 | | * the resolved secrets, where the map file wins on conflicts. |
| | | 11 | | * |
| | | 12 | | * On Windows environment names are case-insensitive, so an inherited `Path` |
| | | 13 | | * and a mapped `PATH` are the same variable and the child must get only one. |
| | | 14 | | * Values are copied as-is: no escaping, quoting or trimming. |
| | | 15 | | */ |
| | | 16 | | export function buildChildEnvironment( |
| | | 17 | | inherited: Readonly<Record<string, string | undefined>>, |
| | | 18 | | resolved: Readonly<Record<string, string>>, |
| | | 19 | | platform: string, |
| | | 20 | | ): ChildEnvironment { |
| | | 21 | | const normalize = |
| | 61 | 22 | | platform === 'win32' |
| | 12 | 23 | | ? (name: string) => name.toUpperCase() |
| | 3978 | 24 | | : (name: string) => name; |
| | 61 | 25 | | const resolvedNames = new Set(Object.keys(resolved).map(normalize)); |
| | | 26 | | |
| | | 27 | | // Null prototype: an inherited variable named `__proto__` must stay an own |
| | | 28 | | // property instead of going through the Object.prototype setter. |
| | 61 | 29 | | const env: Record<string, string> = Object.create(null); |
| | 61 | 30 | | const overridden: string[] = []; |
| | 61 | 31 | | for (const [name, value] of Object.entries(inherited)) { |
| | 3882 | 32 | | if (value === undefined) { |
| | 1 | 33 | | continue; |
| | | 34 | | } |
| | 3881 | 35 | | if (resolvedNames.has(normalize(name))) { |
| | 8 | 36 | | overridden.push(name); |
| | 8 | 37 | | continue; |
| | | 38 | | } |
| | 3873 | 39 | | env[name] = value; |
| | | 40 | | } |
| | 61 | 41 | | for (const [name, value] of Object.entries(resolved)) { |
| | 109 | 42 | | env[name] = value; |
| | | 43 | | } |
| | | 44 | | |
| | 61 | 45 | | return { env, overridden }; |
| | | 46 | | } |
| | | 47 | | |
| | | 48 | | /** |
| | | 49 | | * Groups of variable names that the platform treats as one variable. On |
| | | 50 | | * Windows names are case-insensitive, so `Path` and `PATH` collide; on other |
| | | 51 | | * platforms there are never collisions. |
| | | 52 | | */ |
| | | 53 | | export function findCaseCollisions( |
| | | 54 | | names: readonly string[], |
| | | 55 | | platform: string, |
| | | 56 | | ): string[][] { |
| | 76 | 57 | | if (platform !== 'win32') { |
| | 73 | 58 | | return []; |
| | | 59 | | } |
| | 3 | 60 | | const groups = new Map<string, string[]>(); |
| | 3 | 61 | | for (const name of names) { |
| | 5 | 62 | | const key = name.toUpperCase(); |
| | 5 | 63 | | groups.set(key, [...(groups.get(key) ?? []), name]); |
| | | 64 | | } |
| | 3 | 65 | | return [...groups.values()].filter((group) => group.length > 1); |
| | | 66 | | } |