UNPKG

@varlock/nextjs-integration

Version:

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

207 lines (202 loc) 9.03 kB
'use strict'; var fs = require('fs'); var path = require('path'); var env = require('varlock/env'); var patchServerResponse = require('varlock/patch-server-response'); var patchConsole = require('varlock/patch-console'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var fs__default = /*#__PURE__*/_interopDefault(fs); var path__default = /*#__PURE__*/_interopDefault(path); // src/plugin.ts if (!process.env.__VARLOCK_ENV) { console.error([ "\u{1F6A8} process.env.__VARLOCK_ENV is not set \u{1F6A8}", "", "To use this plugin, you must override @next/env with @varlock/next-integration", "See https://varlock.dev/integrations/nextjs for more information", "" ].join("\n")); throw new Error("VarlockNextWebpackPlugin: __VARLOCK_ENV is not set"); } patchConsole.patchGlobalConsole(); var IS_WORKER = !!process.env.NEXT_PRIVATE_WORKER; function debug(...args) { if (!process.env.DEBUG_VARLOCK_NEXT_INTEGRATION) return; console.log( "plugin", IS_WORKER ? "[worker] " : "[server]", "--", ...args ); } debug("\u2728 LOADED @varlock/next-integration/plugin module!"); var WEBPACK_PLUGIN_NAME = "VarlockNextWebpackPlugin"; var scannedStaticFiles = false; async function scanStaticFiles(nextDirPath) { scannedStaticFiles = true; for await (const file of fs__default.default.promises.glob(`${nextDirPath}/**/*.html`)) { const fileContents = await fs__default.default.promises.readFile(file, "utf8"); env.scanForLeaks(fileContents, { method: "nextjs scan static html files", file }); } } function patchGlobalFsMethods() { const origWriteFileFn = fs__default.default.promises.writeFile; fs__default.default.promises.writeFile = async function dmnoPatchedWriteFile(...args) { const filePath = args[0].toString(); if (filePath.endsWith("/.next/next-server.js.nft.json") && !scannedStaticFiles) { const nextDirPath = filePath.substring(0, filePath.lastIndexOf("/")); await scanStaticFiles(nextDirPath); } return origWriteFileFn.call(this, ...args); }; } var latestLoadedVarlockEnv; var StaticReplacementsProxy = new Proxy({}, { ownKeys(_target) { latestLoadedVarlockEnv = JSON.parse(process.env.__VARLOCK_ENV || "{}"); const replaceKeys = []; for (const itemKey in latestLoadedVarlockEnv.config) { const item = latestLoadedVarlockEnv.config[itemKey]; if (!item.isSensitive) replaceKeys.push(`ENV.${itemKey}`); } debug("reloaded static replacements keys", replaceKeys); return replaceKeys; }, getOwnPropertyDescriptor(_target, prop) { const itemKey = prop.toString().split(".")[1]; const item = latestLoadedVarlockEnv?.config[itemKey]; if (!item || item.isSensitive) return; return { value: "", // this value is not used, the get handler will return the value writable: false, enumerable: true, configurable: true }; }, get(_target, prop) { const itemKey = prop.toString().split(".")[1]; const item = latestLoadedVarlockEnv?.config[itemKey]; if (item && !item.isSensitive) return JSON.stringify(item.value); } }); function varlockNextConfigPlugin(_pluginOptions) { return (nextConfig) => { debug("varlockNextConfigPlugin init fn"); return async (phase, defaults) => { let resolvedNextConfig; if (typeof nextConfig === "function") { const nextConfigFnResult = nextConfig(phase, defaults); resolvedNextConfig = await nextConfigFnResult; } else { resolvedNextConfig = nextConfig; } if (process.env.TURBOPACK || process.env.npm_config_turbopack) { console.error([ "\u{1F6A8} @varlock/nextjs-integration: Turbopack is not yet supported for varlockNextConfigPlugin \u{1F6A8}", "", "You can either stop using the `--turbopack` flag", "or remove this plugin from your config, and only use the @next/env override.", "However if you don't use the plugin, you will not get all the benefits of this integration.", "" ].join("\n")); throw new Error("varlockNextConfigPlugin: Turbopack is not yet supported"); } return { ...resolvedNextConfig, webpack(webpackConfig, options) { debug("varlockNextConfigPlugin webpack config patching"); const { dev } = options; if (env.varlockSettings.preventLeaks) { patchGlobalFsMethods(); patchServerResponse.patchGlobalServerResponse({ // ignore sourcemaps - although we may in future want to scrub them? ignoreUrlPatterns: [/^\/__nextjs_source-map\?.*/], // in dev mode, we redact the secrets rather than throwing, because otherwise the dev server crashes redactInsteadOfThrow: dev }); } const webpack = options.webpack; if (resolvedNextConfig.webpack) { webpackConfig = resolvedNextConfig.webpack(webpackConfig, options); } if (!process.env.__VARLOCK_ENV) throw new Error("VarlockNextWebpackPlugin: __VARLOCK_ENV is not set"); debug("adding ENV.xxx static replacements proxy object"); webpackConfig.plugins.push(new webpack.DefinePlugin(StaticReplacementsProxy)); if (env.varlockSettings.preventLeaks) { webpackConfig.plugins.push({ apply(compiler) { compiler.hooks.assetEmitted.tap(WEBPACK_PLUGIN_NAME, (file, assetDetails) => { const { content, targetPath } = assetDetails; if (targetPath.includes("/.next/static/chunks/") || targetPath.endsWith(".html") || targetPath.endsWith(".body") || targetPath.endsWith(".rsc")) { try { env.scanForLeaks(content, { method: "@varlock/nextjs-integration/plugin - assetEmitted hook", file: targetPath }); } catch (err) { if (dev) { fs__default.default.writeFileSync(targetPath, env.redactSensitiveConfig(content.toString())); } else { throw err; } } } }); } }); } function injectVarlockInitIntoWebpackRuntime(_edgeRuntime = false) { return function assetUpdateFn(origSource) { const origSourceStr = origSource.source(); const injectorPath = path__default.default.resolve(__dirname, "./patch-next-runtime.js"); const injectorSrc = fs__default.default.readFileSync(injectorPath, "utf8"); const updatedSourceStr = [ // TODO: reimplement dynamic/static functionality, which triggers a call to next/headers (or similar) // when accessing a dynamic env item. This will force the page into dynamic rendering mode // see DMNO integration for more details // inline the dmno injector code injectorSrc, origSourceStr ].join("\n"); return new webpack.sources.RawSource(updatedSourceStr); }; } webpackConfig.plugins.push({ apply(compiler) { compiler.hooks.thisCompilation.tap(WEBPACK_PLUGIN_NAME, (compilation) => { compilation.hooks.processAssets.tap( { name: WEBPACK_PLUGIN_NAME, stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS }, () => { if (compilation.getAsset("webpack-runtime.js")) { compilation.updateAsset("webpack-runtime.js", injectVarlockInitIntoWebpackRuntime()); } if (compilation.getAsset("../webpack-runtime.js")) { compilation.updateAsset("../webpack-runtime.js", injectVarlockInitIntoWebpackRuntime()); } if (compilation.getAsset("webpack-api-runtime.js")) { compilation.updateAsset("webpack-api-runtime.js", injectVarlockInitIntoWebpackRuntime()); } if (compilation.getAsset("../webpack-api-runtime.js")) { compilation.updateAsset("../webpack-api-runtime.js", injectVarlockInitIntoWebpackRuntime()); } if (compilation.getAsset("edge-runtime-webpack.js")) { compilation.updateAsset("edge-runtime-webpack.js", injectVarlockInitIntoWebpackRuntime(true)); } } ); }); } }); return webpackConfig; } }; }; }; } exports.varlockNextConfigPlugin = varlockNextConfigPlugin; //# sourceMappingURL=plugin.js.map //# sourceMappingURL=plugin.js.map