(
config: Record<string, string | number | undefined>,
options: UpdateEnvOptions = {},
)
| 180 | } |
| 181 | |
| 182 | export async function updateEnvVariable( |
| 183 | config: Record<string, string | number | undefined>, |
| 184 | options: UpdateEnvOptions = {}, |
| 185 | ): Promise<void> { |
| 186 | const { envFile, backupFile, tempFile } = resolvePaths(options); |
| 187 | const createBackup = options.createBackup ?? false; |
| 188 | const restoreFromBackup = options.restoreFromBackup ?? false; |
| 189 | |
| 190 | try { |
| 191 | const appliedEntries = Object.entries(config).filter( |
| 192 | (entry): entry is [string, string | number] => entry[1] !== undefined, |
| 193 | ); |
| 194 | if (appliedEntries.length === 0) { |
| 195 | return; |
| 196 | } |
| 197 | |
| 198 | const existingText = await readEnv(envFile); |
| 199 | const parsedEnv = dotenv.parse(existingText); |
| 200 | |
| 201 | let changed = false; |
| 202 | for (const [key, rawValue] of appliedEntries) { |
| 203 | const value = String(rawValue); |
| 204 | if (parsedEnv[key] !== value) { |
| 205 | parsedEnv[key] = value; |
| 206 | changed = true; |
| 207 | } |
| 208 | // Intentionally sync the running process environment for every key in |
| 209 | // `appliedEntries`, even when `parsedEnv` already matches and `changed` |
| 210 | // stays false (no file rewrite). We may return early on the no-op path, |
| 211 | // but callers still expect `process.env` to reflect the effective env |
| 212 | // state represented by `parsedEnv`. |
| 213 | process.env[key] = value; |
| 214 | } |
| 215 | |
| 216 | // Preserve the existing file (and its formatting/comments) when the update |
| 217 | // results in no changes. |
| 218 | if (!changed) { |
| 219 | return; |
| 220 | } |
| 221 | |
| 222 | if (createBackup) { |
| 223 | await ensureBackup(envFile, backupFile); |
| 224 | } |
| 225 | |
| 226 | // When we do have changes, we intentionally rewrite the file in a canonical |
| 227 | // format (dotenv.parse + serialize). This will drop comments/blank lines. |
| 228 | const nextContent = serializeEnvVars(parsedEnv); |
| 229 | await writeTemp(tempFile, nextContent); |
| 230 | await commitTemp(envFile, tempFile); |
| 231 | } catch (e: unknown) { |
| 232 | await cleanupTemp(tempFile); |
| 233 | if (restoreFromBackup) { |
| 234 | try { |
| 235 | await restoreBackup(envFile, backupFile); |
| 236 | } catch (restoreErr: unknown) { |
| 237 | throw new SetupError( |
| 238 | SetupErrorCode.RESTORE_FAILED, |
| 239 | "Update failed and backup restoration also failed", |
no test coverage detected