(projectId: string)
| 217 | } |
| 218 | |
| 219 | export async function syncEnvFileToDb(projectId: string): Promise<number> { |
| 220 | const project = await ensureProject(projectId); |
| 221 | const repoEnvPath = envFilePath(project); |
| 222 | |
| 223 | let fileContents = ''; |
| 224 | try { |
| 225 | fileContents = await fs.readFile(repoEnvPath, 'utf8'); |
| 226 | } catch (error) { |
| 227 | if ((error as NodeJS.ErrnoException).code === 'ENOENT') { |
| 228 | return 0; |
| 229 | } |
| 230 | throw error; |
| 231 | } |
| 232 | |
| 233 | const fileVars = parseEnvFile(fileContents); |
| 234 | const existingVars = await prisma.envVar.findMany({ |
| 235 | where: { projectId }, |
| 236 | }); |
| 237 | |
| 238 | const existingMap = new Map(existingVars.map((envVar) => [envVar.key, envVar])); |
| 239 | const fileKeys = new Set(Object.keys(fileVars)); |
| 240 | let changes = 0; |
| 241 | |
| 242 | for (const [key, value] of Object.entries(fileVars)) { |
| 243 | const current = existingMap.get(key); |
| 244 | if (current) { |
| 245 | let currentValue: string | null = null; |
| 246 | try { |
| 247 | currentValue = decrypt(current.valueEncrypted); |
| 248 | } catch (error) { |
| 249 | console.warn(`[EnvService] Failed to decrypt env var ${current.key}:`, error); |
| 250 | } |
| 251 | if (currentValue !== value) { |
| 252 | await prisma.envVar.update({ |
| 253 | where: { |
| 254 | projectId_key: { projectId, key }, |
| 255 | }, |
| 256 | data: { valueEncrypted: encrypt(value) }, |
| 257 | }); |
| 258 | changes += 1; |
| 259 | } |
| 260 | } else { |
| 261 | await prisma.envVar.create({ |
| 262 | data: { |
| 263 | projectId, |
| 264 | key, |
| 265 | valueEncrypted: encrypt(value), |
| 266 | scope: 'runtime', |
| 267 | varType: 'string', |
| 268 | isSecret: true, |
| 269 | }, |
| 270 | }); |
| 271 | changes += 1; |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | for (const envVar of existingVars) { |
| 276 | if (!fileKeys.has(envVar.key)) { |
no test coverage detected