@varlock/nextjs-integration 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/loader.js +88 -0
- package/dist/loader.js.map +1 -0
- package/dist/next-env-compat.js +17560 -101
- package/dist/next-env-compat.js.map +1 -1
- package/dist/plugin.js +434 -141
- package/dist/plugin.js.map +1 -1
- package/package.json +16 -16
- package/LICENSE +0 -21
- package/dist/patch-next-runtime.js +0 -14
- package/dist/patch-next-runtime.js.map +0 -1
- /package/dist/{patch-next-runtime.d.ts → loader.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -4,12 +4,15 @@ This package helps you integrate [varlock](https://varlock.dev) into a [Next.js]
|
|
|
4
4
|
|
|
5
5
|
It is designed as a drop-in replacement for [`@next/env`](https://www.npmjs.com/package/@next/env), which is the internal package that Next.js uses to load `.env` files, as well as a small plugin for your `next.config.*` file that enables additional security features.
|
|
6
6
|
|
|
7
|
+
It supports Next.js v14+, including full support for both **Next.js 15** and **16** with both **Webpack** and **Turbopack** bundlers.
|
|
8
|
+
|
|
7
9
|
Compared to the default `@next/env` behavior, this package provides:
|
|
8
10
|
|
|
9
11
|
- validation of your env vars against your `.env.schema`
|
|
10
12
|
- type-generation and type-safe env var access with built-in docs
|
|
11
13
|
- redaction of sensitive values from application logs
|
|
12
14
|
- leak detection and prevention, both at build and runtime
|
|
15
|
+
- sourcemap scrubbing to prevent secrets leaking in production source maps
|
|
13
16
|
- more flexible multi-env handling - you can load env-specific files other than `.env.development`/`.env.production`
|
|
14
17
|
|
|
15
18
|
See [our docs site](https://varlock.dev/integrations/nextjs/) for complete installation and usage instructions.
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
5
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/loader.ts
|
|
9
|
+
var require_loader = __commonJS({
|
|
10
|
+
"src/loader.ts"(exports$1, module) {
|
|
11
|
+
function debug(...args) {
|
|
12
|
+
if (!process.env.DEBUG_VARLOCK_NEXT_INTEGRATION) return;
|
|
13
|
+
console.log("[varlock-loader]", ...args);
|
|
14
|
+
}
|
|
15
|
+
function isTurbopackWorker() {
|
|
16
|
+
return !!(process.env.TURBOPACK || process.env.TURBOPACK_DEV || process.env.TURBOPACK_BUILD || process.env.npm_config_turbopack);
|
|
17
|
+
}
|
|
18
|
+
function escapeRegExp(str) {
|
|
19
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
}
|
|
21
|
+
var USE_CLIENT_RE = /^(?:[^\S\n]*\/\/[^\n]*\n|[^\S\n]*\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[^\S\n]*\n|[^\S\n]*\n)*[^\S\n]*['"]use client['"]/;
|
|
22
|
+
var DIRECTIVE_PROLOGUE_RE = /^((?:[^\S\n]*\/\/[^\n]*\n|[^\S\n]*\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[^\S\n]*\n|[^\S\n]*\n)*[^\S\n]*['"]use (?:server|client|strict)['"][^\S\n]*;?[^\S\n]*\n?)/;
|
|
23
|
+
function prependAfterDirectives(source, codeToPrepend) {
|
|
24
|
+
const match = source.match(DIRECTIVE_PROLOGUE_RE);
|
|
25
|
+
if (match) {
|
|
26
|
+
return `${match[1] + codeToPrepend}
|
|
27
|
+
${source.slice(match[1].length)}`;
|
|
28
|
+
}
|
|
29
|
+
return `${codeToPrepend}
|
|
30
|
+
${source}`;
|
|
31
|
+
}
|
|
32
|
+
function webpackLoader(source) {
|
|
33
|
+
const projectRoot = this.rootContext || process.cwd();
|
|
34
|
+
if (!this.resourcePath.startsWith(projectRoot)) {
|
|
35
|
+
return source;
|
|
36
|
+
}
|
|
37
|
+
const relPath = this.resourcePath.slice(projectRoot.length);
|
|
38
|
+
if (relPath.includes("/node_modules/") || relPath.includes("\\node_modules\\")) {
|
|
39
|
+
return source;
|
|
40
|
+
}
|
|
41
|
+
debug("processing:", relPath);
|
|
42
|
+
const isClientComponent = USE_CLIENT_RE.test(source);
|
|
43
|
+
if (isClientComponent) debug("client component:", relPath);
|
|
44
|
+
const rawEnv = process.env.__VARLOCK_ENV;
|
|
45
|
+
if (!rawEnv) {
|
|
46
|
+
throw new Error("expected __VARLOCK_ENV to be set");
|
|
47
|
+
}
|
|
48
|
+
let envGraph;
|
|
49
|
+
try {
|
|
50
|
+
envGraph = JSON.parse(rawEnv);
|
|
51
|
+
} catch {
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
const loaderOptions = this.getOptions?.() ?? {};
|
|
55
|
+
const isWebpack = loaderOptions.bundler === "webpack";
|
|
56
|
+
const isTurbopack = loaderOptions.bundler === "turbopack" || isTurbopackWorker();
|
|
57
|
+
const isEdge = loaderOptions.isEdge ?? false;
|
|
58
|
+
let result = source;
|
|
59
|
+
if (!isClientComponent) {
|
|
60
|
+
let initGuard;
|
|
61
|
+
if (isEdge) {
|
|
62
|
+
initGuard = "if(globalThis.__varlockPatchConsole)globalThis.__varlockPatchConsole();";
|
|
63
|
+
} else {
|
|
64
|
+
initGuard = "if(!globalThis.__varlockBuildInit){globalThis.__varlockBuildInit=true;require('varlock/env').initVarlockEnv();require('varlock/patch-console').patchGlobalConsole();}";
|
|
65
|
+
if (isWebpack) {
|
|
66
|
+
initGuard += "require('varlock/patch-console').patchGlobalConsole();";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
result = prependAfterDirectives(result, initGuard);
|
|
70
|
+
}
|
|
71
|
+
if (isTurbopack && source.includes("ENV.")) {
|
|
72
|
+
this.cacheable(false);
|
|
73
|
+
for (const [key, item] of Object.entries(envGraph.config)) {
|
|
74
|
+
if (item.isSensitive) continue;
|
|
75
|
+
const pattern = new RegExp(`\\bENV\\.${escapeRegExp(key)}(?![\\w$])`, "g");
|
|
76
|
+
result = result.replace(pattern, JSON.stringify(item.value));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
module.exports = webpackLoader;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
var loader = require_loader();
|
|
85
|
+
|
|
86
|
+
module.exports = loader;
|
|
87
|
+
//# sourceMappingURL=loader.js.map
|
|
88
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/loader.ts"],"names":["exports"],"mappings":"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","file":"loader.js","sourcesContent":["import type { SerializedEnvGraph } from 'varlock';\n\nfunction debug(...args: Array<any>) {\n if (!process.env.DEBUG_VARLOCK_NEXT_INTEGRATION) return;\n // eslint-disable-next-line no-console\n console.log('[varlock-loader]', ...args);\n}\n\ntype LoaderContext = {\n cacheable(flag: boolean): void;\n resourcePath: string;\n rootContext: string;\n getOptions?(): { bundler?: 'webpack' | 'turbopack'; isEdge?: boolean };\n};\n\nfunction isTurbopackWorker() {\n return !!(\n process.env.TURBOPACK\n || process.env.TURBOPACK_DEV\n || process.env.TURBOPACK_BUILD\n || process.env.npm_config_turbopack\n );\n}\n\nfunction escapeRegExp(str: string) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// detect 'use client' directive at top of file (before any code, after optional comments)\n// uses [^\\S\\n]* (horizontal whitespace) instead of \\s* to avoid exponential backtracking\nconst USE_CLIENT_RE = /^(?:[^\\S\\n]*\\/\\/[^\\n]*\\n|[^\\S\\n]*\\/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*\\/[^\\S\\n]*\\n|[^\\S\\n]*\\n)*[^\\S\\n]*['\"]use client['\"]/;\n\n// match directive prologue ('use server', 'use client', etc.) + optional trailing semicolons/newlines\n// captures the directive block so we can inject code after it\nconst DIRECTIVE_PROLOGUE_RE = /^((?:[^\\S\\n]*\\/\\/[^\\n]*\\n|[^\\S\\n]*\\/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*\\/[^\\S\\n]*\\n|[^\\S\\n]*\\n)*[^\\S\\n]*['\"]use (?:server|client|strict)['\"][^\\S\\n]*;?[^\\S\\n]*\\n?)/;\n\n/** Prepend code after any directive prologue (e.g. 'use server') to avoid breaking it */\nfunction prependAfterDirectives(source: string, codeToPrepend: string): string {\n const match = source.match(DIRECTIVE_PROLOGUE_RE);\n if (match) {\n return `${match[1] + codeToPrepend}\\n${source.slice(match[1].length)}`;\n }\n return `${codeToPrepend}\\n${source}`;\n}\n\n/**\n * Webpack/Turbopack loader that:\n * 1. Injects resolved env config into instrumentation and proxy files.\n * 2. Replaces `ENV.KEY` references for non-sensitive vars with literal JSON values.\n *\n * SECURITY: Sensitive env values are ONLY embedded into server-side files\n * (never client components). The static ENV.KEY replacements explicitly skip\n * sensitive vars.\n */\nfunction webpackLoader(this: LoaderContext, source: string) {\n // only transform files within the project root\n // this skips node_modules AND symlinked workspace packages (e.g. varlock/env)\n // which turbopack resolves to their real paths outside node_modules\n const projectRoot = this.rootContext || process.cwd();\n if (!this.resourcePath.startsWith(projectRoot)) {\n return source;\n }\n // still skip node_modules within the project root\n const relPath = this.resourcePath.slice(projectRoot.length);\n if (relPath.includes('/node_modules/') || relPath.includes('\\\\node_modules\\\\')) {\n return source;\n }\n\n debug('processing:', relPath);\n\n const isClientComponent = USE_CLIENT_RE.test(source);\n if (isClientComponent) debug('client component:', relPath);\n\n const rawEnv = process.env.__VARLOCK_ENV;\n if (!rawEnv) {\n throw new Error('expected __VARLOCK_ENV to be set');\n }\n\n // TODO: avoid parsing on every file\n let envGraph: SerializedEnvGraph;\n try {\n envGraph = JSON.parse(rawEnv);\n } catch {\n return source;\n }\n\n const loaderOptions = this.getOptions?.() ?? {};\n\n const isWebpack = loaderOptions.bundler === 'webpack';\n const isTurbopack = loaderOptions.bundler === 'turbopack' || isTurbopackWorker();\n const isEdge = loaderOptions.isEdge ?? false;\n\n let result = source;\n\n if (!isClientComponent) {\n // Inject a tiny guarded init snippet into every server file.\n // Pre-rendering workers receive compiled code via IPC (not from disk), so runtime\n // file injection doesn't help them. This ensures initVarlockEnv() and\n // patchGlobalConsole() run once per process — the globalThis guard makes it\n // idempotent, and turbopack deduplicates the require() targets.\n let initGuard: string;\n if (isEdge) {\n // Edge compilation: can't use require(), so use globalThis.__varlockPatchConsole\n // which the init-edge bundle exposes. The init guard is skipped since edge init\n // is handled by the runtime file injection (processAssets hook).\n initGuard = 'if(globalThis.__varlockPatchConsole)globalThis.__varlockPatchConsole();';\n } else {\n initGuard = 'if(!globalThis.__varlockBuildInit){globalThis.__varlockBuildInit=true;require(\\'varlock/env\\').initVarlockEnv();require(\\'varlock/patch-console\\').patchGlobalConsole();}';\n // When used from webpack, React wraps console for RSC dev replay AFTER our initial\n // patch in the runtime file. Re-patching outside the once-guard ensures our redaction\n // wraps React's wrapper so secrets are redacted before React captures them.\n // patchGlobalConsole() no-ops if console.log still has _varlockPatchedFn.\n if (isWebpack) {\n initGuard += 'require(\\'varlock/patch-console\\').patchGlobalConsole();';\n }\n }\n result = prependAfterDirectives(result, initGuard);\n }\n\n // static replacements for non-sensitive env vars\n // webpack uses DefinePlugin for this, so only needed for turbopack\n if (isTurbopack && source.includes('ENV.')) {\n // disable caching only for files that reference ENV — their output depends on env values\n this.cacheable(false);\n for (const [key, item] of Object.entries(envGraph.config)) {\n if (item.isSensitive) continue;\n\n // TODO: smarter replacement (vite version uses AST?)\n\n // match ENV.KEY as a member expression (word boundary before ENV, not followed by more identifier chars)\n const pattern = new RegExp(`\\\\bENV\\\\.${escapeRegExp(key)}(?![\\\\w$])`, 'g');\n result = result.replace(pattern, JSON.stringify(item.value));\n }\n }\n\n return result;\n}\n\n// CJS export required for webpack loader-runner compatibility\nmodule.exports = webpackLoader;\n"]}
|