/** * The goal here is to sync the .env file with the .env.template file, in every package. * is a special value that will be replaced with the value from the root .env file. */ import { constants } from 'fs'; import { access, readFile, writeFile } from 'fs/promises'; import { dirname, join, relative } from 'path'; import { parse } from 'dotenv'; import glob from 'glob'; if (process.env.CI) { console.log('[sync-env-files] CI Detected, skipping'); process.exit(0); } const cwd = process.cwd(); async function main() { console.log('[sync-env-files] Syncing'); const [localFiles, rootEnv] = await Promise.all([findLocalEnvFiles(), loadRootEnv()]); async function syncEnvFile(envLocalFile) { const dir = dirname(envLocalFile); const envFile = join(dir, '.env'); if (!(await exists(envFile))) { console.log('[sync-env-files] Write .env file in', relative(process.cwd(), dir)); await writeFile(envFile, await readFile(envLocalFile)); } // compare the contents of the local file and the env file const [localFileContents, envFileContents] = await Promise.all([ readFile(envLocalFile, 'utf8'), readFile(envFile, 'utf8'), ]); const localEnv = parse(localFileContents); const env = parse(envFileContents); let modified = false; for (const [key, value] of Object.entries(localEnv)) { if (!(key in env)) { modified = true; env[key] = value; } if (env[key] === '' || env[key] === '') { modified = true; env[key] = typeof rootEnv[key] !== 'undefined' ? rootEnv[key] : process.env[key]; } } if (modified) { console.log('[sync-env-files] Sync', relative(process.cwd(), envFile)); await writeFile(envFile, stringifyDotEnv(env), 'utf8'); } } await Promise.all(localFiles.map(syncEnvFile)); console.log('[sync-env-files] Synced'); } main().catch(error => { console.error(error); process.exit(1); }); async function exists(file) { try { await access(file, constants.F_OK); return true; } catch { return false; } } /** * @returns {Promise} */ function findLocalEnvFiles() { return new Promise((resolve, reject) => { glob( '{packages/**/*/.env.template,integration-tests/.env.template}', { ignore: ['**/node_modules/**', '**/dist/**'], cwd, }, (err, files) => { if (err) { reject(err); } else { resolve(files); } }, ); }); } async function loadRootEnv() { const rootEnvFile = join(cwd, '.env'); return (await exists(rootEnvFile)) ? parse(await readFile(rootEnvFile, 'utf8')) : {}; } function stringifyDotEnv(obj) { const quote = /[\s"']/; if (typeof obj !== 'object') { throw new Error('stringify() expects an object'); } return Object.keys(obj) .map(key => { const val = obj[key]; let str = ''; switch (typeof val) { case 'string': try { JSON.parse(val); str = val; } catch (e) { str = quote.test(val) ? JSON.stringify(val) : val; } break; case 'boolean': case 'number': str = String(val); break; case 'undefined': str = ''; break; case 'object': if (val !== null) { str = JSON.stringify(val); } break; } return `${key}=${str}`; }) .join('\n'); }