@varlock/nextjs-integration
Version:
drop-in replacement for @next/env that uses varlock to load .env files with validation and extra security features
240 lines (235 loc) • 8.88 kB
JavaScript
;
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');
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 envFiles = varlockLoadedEnv.sources.filter((s) => s.enabled && s.label !== "process.env").map((s) => ({
path: s.label,
contents: "",
env: {}
}));
if (envFiles.length) {
envFiles.push({ path: "\n \u2728 loaded by varlock \u2728", contents: "", env: {} });
}
return envFiles;
}
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, "# trigger reload", "utf-8");
setTimeout(() => {
fs__namespace.unlinkSync(envFilePathToUpdate);
}, 500);
} 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);
}
}
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 {
const varlockLoadedEnvBuf = child_process.execSync(`varlock load --format json-full --env ${envFromNextCommand}`, {
env: exports.initialEnv
});
varlockLoadedEnv = JSON.parse(varlockLoadedEnvBuf.toString());
} catch (err) {
const { stdout, stderr } = err;
const stdoutStr = stdout?.toString() || "";
const stderrStr = stderr?.toString() || "";
if (stderrStr.includes("command not found")) {
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"));
throw new Error("missing peer dependency - varlock");
}
if (stdoutStr) console.log(stdoutStr);
if (stderrStr) console.error(stderrStr);
if (!dev) 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