@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
JavaScript
;
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