UNPKG

@varlock/nextjs-integration

Version:

drop-in replacement for @next/env that uses varlock to load .env files with validation and extra security features

250 lines (245 loc) 9.26 kB
'use strict'; var fs = require('fs'); var path = require('path'); var child_process = require('child_process'); var env = require('varlock/env'); var patchConsole = require('varlock/patch-console'); var execSyncVarlock = require('varlock/exec-sync-varlock'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var fs__namespace = /*#__PURE__*/_interopNamespace(fs); var path__namespace = /*#__PURE__*/_interopNamespace(path); // src/next-env-compat.ts exports.initialEnv = void 0; var lastReloadAt; var varlockLoadedEnv; var combinedEnv; var parsedEnv; var loadedEnvFiles = []; var rootDir; function getVarlockSourcesAsLoadedEnvFiles() { const envFilesLabels = varlockLoadedEnv.sources.filter((s) => s.enabled && !s.label.startsWith("directory -")).map((s) => s.label); if (envFilesLabels.length) { envFilesLabels.push("\n \u2728 loaded by varlock \u2728"); } const uniqueLabels = [...new Set(envFilesLabels)]; return uniqueLabels.map((label) => ({ path: label, contents: "", env: {} })); } var IS_WORKER = !!process.env.NEXT_PRIVATE_WORKER; function debug(...args) { if (!process.env.DEBUG_VARLOCK_NEXT_INTEGRATION) return; console.log( IS_WORKER ? "worker -- " : "server -- ", ...args ); } debug("\u2728 LOADED @next/env module!"); var extraWatcherEnabled = false; var NEXT_WATCHED_ENV_FILES = [".env", ".env.local", ".env.development", ".env.development.local"]; function enableExtraFileWatchers() { if (extraWatcherEnabled || IS_WORKER) return; extraWatcherEnabled = true; if (!rootDir) throw new Error("expected rootDir to be set"); const envSchemaPath = path__namespace.join(rootDir, ".env.schema"); let envFilePathToUpdate = null; for (const envFileName of NEXT_WATCHED_ENV_FILES) { const filePath = path__namespace.join(rootDir, envFileName); if (fs__namespace.existsSync(filePath)) { envFilePathToUpdate = filePath; break; } } let destroyFile = false; if (!envFilePathToUpdate) { envFilePathToUpdate ||= path__namespace.join(rootDir, ".env"); destroyFile = true; } debug("set up extra file watchers", envFilePathToUpdate, destroyFile); fs__namespace.watchFile(envSchemaPath, { interval: 500 }, (_curr, _prev) => { debug(".env.schema changed", envFilePathToUpdate, destroyFile); if (destroyFile) { fs__namespace.writeFileSync(envFilePathToUpdate, [ "# This file was created by @varlock/nextjs-integration", "# It is used to trigger Next.js to reload when non-standard .env files change", "# You can safely ignore and delete it", "# @disable", "# ---" ].join("\n"), "utf-8"); setTimeout(() => { fs__namespace.unlinkSync(envFilePathToUpdate); }, 1e3); } else { const currentContents = fs__namespace.readFileSync(envFilePathToUpdate, "utf-8"); fs__namespace.writeFileSync(envFilePathToUpdate, currentContents, "utf-8"); } }); } function detectOpenNextCloudflareBuild() { try { const pppid = parseInt(child_process.execSync(`ps -o ppid= -p ${process.ppid}`).toString().trim()); const commandName = child_process.execSync(`ps -p ${pppid} -o command`).toString().split("\n")[1]; if (commandName.endsWith(".bin/opennextjs-cloudflare build")) { return true; } } catch (err) { } return false; } function writeResolvedEnvFile() { const dotEnvStrLines = []; for (const [itemKey, itemInfo] of Object.entries(varlockLoadedEnv.config)) { if (itemInfo.value !== void 0) dotEnvStrLines.push(`${itemKey}=${JSON.stringify(itemInfo.value)}`); } if (detectOpenNextCloudflareBuild() || process.env.VERCEL || process.env.WORKERS_CI || process.env._VARLOCK_EXPORT_RESOLVED_ENV_FILE) { dotEnvStrLines.unshift(` # \u{1F6D1} DO NOT CHECK THIS FILE INTO VERSION CONTROL \u{1F6D1} # This file was automatically generated by @varlock/nextjs-integration # It contains a _fully resolved env_ to pass to platforms (ex: vercel, cloudflare, etc) # that are doing their own magic when booting up nextjs in certain scenarios # # It likely contains sensitive config data and should be deleted after use # # @disable # tells varlock to ignore this file # --- `); dotEnvStrLines.push(`__VARLOCK_ENV=${JSON.stringify(varlockLoadedEnv)}`); let resolvedEnvFileName = ".env.production.local"; if (process.env._VARLOCK_EXPORT_RESOLVED_ENV_FILE && !["true", "1"].includes(process.env._VARLOCK_EXPORT_RESOLVED_ENV_FILE)) { resolvedEnvFileName = process.env._VARLOCK_EXPORT_RESOLVED_ENV_FILE; } if (!rootDir) throw new Error("expected rootDir to be set"); fs__namespace.writeFileSync(path__namespace.resolve(rootDir, resolvedEnvFileName), dotEnvStrLines.join("\n"), "utf-8"); } } function updateInitialEnv(newEnv) { if (Object.keys(newEnv).length) { debug("updateInitialEnv", newEnv); Object.assign(exports.initialEnv || {}, newEnv); } } function replaceProcessEnv(sourceEnv) { Object.keys(process.env).forEach((key) => { if (!key.startsWith("__NEXT_PRIVATE")) { if (sourceEnv[key] === void 0 || sourceEnv[key] === "") { delete process.env[key]; } } }); Object.entries(sourceEnv).forEach(([key, value]) => { process.env[key] = value; }); } function processEnv(_loadedEnvFiles, _dir, _log = console, _forceReload = false, _onReload) { return [process.env]; } function resetEnv() { if (exports.initialEnv) { replaceProcessEnv(exports.initialEnv); } } var loadCount = 0; function loadEnvConfig(dir, dev, _log = console, forceReload = false, _onReload) { exports.initialEnv ||= { ...process.env }; debug("loadEnvConfig!", "forceReload = ", forceReload); rootDir ||= dir; if (rootDir !== dir) throw new Error("root directory changed"); if (dev) enableExtraFileWatchers(); let useCachedEnv = !!process.env.__VARLOCK_ENV; if (forceReload) { if (!lastReloadAt) { lastReloadAt = /* @__PURE__ */ new Date(); } else if (lastReloadAt.getTime() < Date.now() - 1e3) { lastReloadAt = /* @__PURE__ */ new Date(); useCachedEnv = false; } } if (useCachedEnv) { if (!varlockLoadedEnv) { varlockLoadedEnv = JSON.parse(process.env.__VARLOCK_ENV || "{}"); parsedEnv = Object.fromEntries( Object.entries(varlockLoadedEnv.config).map(([key, value]) => [key, value.value]) ); env.resetRedactionMap(varlockLoadedEnv); debug("patching console with varlock redactor"); patchConsole.patchGlobalConsole(); } combinedEnv = { ...exports.initialEnv, ...parsedEnv }; debug(">> USING CACHED ENV"); return { combinedEnv, parsedEnv, loadedEnvFiles }; } lastReloadAt = /* @__PURE__ */ new Date(); debug(">> RELOADING ENV"); replaceProcessEnv(exports.initialEnv); let envFromNextCommand = dev ? "development" : "production"; if (process.env.NODE_ENV === "test") envFromNextCommand = "test"; debug("Inferred env mode (to match @next/env):", envFromNextCommand); try { loadCount++; const varlockLoadedEnvStr = execSyncVarlock.execSyncVarlock(`load --format json-full --env ${envFromNextCommand}`, { showLogsOnError: true, // in a build, we want to fail and exit, while in dev we can keep retrying when changes are detected exitOnError: !dev, env: exports.initialEnv }); if (loadCount >= 2) { console.log("\u2705 env reloaded and validated"); } varlockLoadedEnv = JSON.parse(varlockLoadedEnvStr); } catch (err) { if (err.message.includes("Unable to find varlock executable")) { console.error([ "", "\u274C ERROR: varlock not found", "varlock is a required peer dependency of @varlock/nextjs-integration", "", "Please add varlock as a dependency to your project (e.g., `npm install varlock`)" ].join("\n")); process.exit(1); } process.env.__VARLOCK_ENV = JSON.stringify({ sources: [], config: {}, settings: {} }); return { combinedEnv: {}, parsedEnv: {}, loadedEnvFiles: [] }; } parsedEnv = {}; for (const [itemKey, itemInfo] of Object.entries(varlockLoadedEnv.config)) { parsedEnv[itemKey] = itemInfo.value; } debug("LOADED ENV:", parsedEnv); process.env.__VARLOCK_ENV = JSON.stringify(varlockLoadedEnv); env.initVarlockEnv(); env.resetRedactionMap(varlockLoadedEnv); debug("patching console with varlock redactor"); patchConsole.patchGlobalConsole(); combinedEnv = { ...exports.initialEnv, ...parsedEnv }; loadedEnvFiles = getVarlockSourcesAsLoadedEnvFiles(); if (!dev) writeResolvedEnvFile(); return { combinedEnv, parsedEnv, loadedEnvFiles }; } exports.loadEnvConfig = loadEnvConfig; exports.processEnv = processEnv; exports.resetEnv = resetEnv; exports.updateInitialEnv = updateInitialEnv; //# sourceMappingURL=next-env-compat.js.map //# sourceMappingURL=next-env-compat.js.map