@vite-env/core 0.3.0 → 0.5.0
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 +18 -0
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/dts.d.cts +1 -1
- package/dist/dts.d.mts +1 -1
- package/dist/format.cjs +82 -0
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.cts +41 -2
- package/dist/format.d.mts +41 -2
- package/dist/format.mjs +78 -1
- package/dist/format.mjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/leak.d.cts +1 -1
- package/dist/leak.d.mts +1 -1
- package/dist/plugin.cjs +101 -4
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +22 -0
- package/dist/plugin.d.mts +22 -0
- package/dist/plugin.mjs +101 -5
- package/dist/plugin.mjs.map +1 -1
- package/dist/presets/index.cjs +75 -0
- package/dist/presets/index.cjs.map +1 -0
- package/dist/presets/index.d.cts +85 -0
- package/dist/presets/index.d.mts +85 -0
- package/dist/presets/index.mjs +71 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/schema.cjs +25 -4
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +5 -2
- package/dist/schema.d.mts +5 -2
- package/dist/schema.mjs +25 -4
- package/dist/schema.mjs.map +1 -1
- package/dist/standard.d.cts +1 -1
- package/dist/standard.d.mts +1 -1
- package/dist/{types-1okexcwM.d.cts → types-Bl3YdPck.d.cts} +6 -2
- package/dist/{types-DuWT_251.d.mts → types-CUF5RARc.d.mts} +6 -2
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -6,8 +6,10 @@ The `env.ts` layer for Vite — define once, validate everywhere, import with ty
|
|
|
6
6
|
|
|
7
7
|
- Typed virtual modules (`virtual:env/client`, `virtual:env/server`)
|
|
8
8
|
- Server/client split with build-time leak detection
|
|
9
|
+
- Runtime access protection — warns or errors when `virtual:env/server` is imported from a client environment
|
|
9
10
|
- Auto-coercion via Zod v4 (`z.stringbool()`, `z.coerce.number()`)
|
|
10
11
|
- Standard Schema support — use Valibot, ArkType, or any compliant validator
|
|
12
|
+
- Platform presets — pre-built schemas for Vercel, Railway, and Netlify
|
|
11
13
|
- Auto `.d.ts` generation
|
|
12
14
|
- Vite 8 / Rolldown native
|
|
13
15
|
|
|
@@ -61,6 +63,22 @@ env.VITE_DARK_MODE // boolean
|
|
|
61
63
|
env.VITE_NODE_ENV // 'development' | 'test' | 'production'
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
### Platform presets
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { defineEnv } from '@vite-env/core'
|
|
70
|
+
import { vercel } from '@vite-env/core/presets'
|
|
71
|
+
import { z } from 'zod'
|
|
72
|
+
|
|
73
|
+
export default defineEnv({
|
|
74
|
+
presets: [vercel],
|
|
75
|
+
server: { DATABASE_URL: z.url() },
|
|
76
|
+
client: { VITE_API_URL: z.url() },
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Available presets: `vercel`, `railway`, `netlify`.
|
|
81
|
+
|
|
64
82
|
See the [full documentation](https://pyyupsk.github.io/vite-env/) for server/client split details, CLI tools, and more.
|
|
65
83
|
|
|
66
84
|
## License
|
package/dist/config.d.cts
CHANGED
package/dist/config.d.mts
CHANGED
package/dist/dts.d.cts
CHANGED
package/dist/dts.d.mts
CHANGED
package/dist/format.cjs
CHANGED
|
@@ -10,8 +10,90 @@ function formatStandardSchemaError(issues) {
|
|
|
10
10
|
return ` \x1B[31m✗\x1B[0m ${(issue.path.length > 0 ? issue.path.map((seg) => typeof seg === "object" && seg !== null && "key" in seg ? String(seg.key) : String(seg)).join(".") : "(root)").padEnd(28)} ${issue.message}`;
|
|
11
11
|
}).join("\n");
|
|
12
12
|
}
|
|
13
|
+
const YELLOW = "\x1B[33m";
|
|
14
|
+
const BOLD_YELLOW = "\x1B[1;33m";
|
|
15
|
+
const CYAN = "\x1B[36m";
|
|
16
|
+
const RESET = "\x1B[0m";
|
|
17
|
+
const BOX_WIDTH = 66;
|
|
18
|
+
const CONTENT_AREA = BOX_WIDTH - 4;
|
|
19
|
+
/** Maximum visible character length for an importer path inside the warning box. */
|
|
20
|
+
const IMPORTER_MAX_LEN = CONTENT_AREA - 10;
|
|
21
|
+
/**
|
|
22
|
+
* Truncates an importer path to fit within maxLen visible characters.
|
|
23
|
+
* Adds a leading '…' when truncation occurs so the rightmost (most specific) part is preserved.
|
|
24
|
+
*/
|
|
25
|
+
function truncateImporter(importerPath, maxLen) {
|
|
26
|
+
if (importerPath.length <= maxLen) return importerPath;
|
|
27
|
+
return `\u2026${importerPath.slice(-(maxLen - 1))}`;
|
|
28
|
+
}
|
|
29
|
+
function boxTop() {
|
|
30
|
+
return `${YELLOW}\u250C${"─".repeat(BOX_WIDTH - 2)}\u2510${RESET}`;
|
|
31
|
+
}
|
|
32
|
+
function boxBottom() {
|
|
33
|
+
return `${YELLOW}\u2514${"─".repeat(BOX_WIDTH - 2)}\u2518${RESET}`;
|
|
34
|
+
}
|
|
35
|
+
function boxLine(visibleText, renderedText = visibleText) {
|
|
36
|
+
return `${YELLOW}\u2502${RESET} ${renderedText}${" ".repeat(Math.max(0, CONTENT_AREA - visibleText.length))}${YELLOW}\u2502${RESET}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Renders the colored terminal warning box for 'warn' mode.
|
|
40
|
+
* Every line is exactly 66 characters wide (visible chars, excluding ANSI codes),
|
|
41
|
+
* assuming Vite environment names are short (≤19 chars). Longer names will extend
|
|
42
|
+
* the line beyond 66 chars without crashing — Math.max(0) prevents negative padding.
|
|
43
|
+
* Use in logger.warn() calls — not in log files.
|
|
44
|
+
*/
|
|
45
|
+
function formatGuardWarning(fail) {
|
|
46
|
+
const importerDisplay = fail.importer === void 0 ? "(unknown)" : truncateImporter(fail.importer, 52);
|
|
47
|
+
return [
|
|
48
|
+
boxTop(),
|
|
49
|
+
boxLine("[vite-env] DEPRECATION WARNING", `${BOLD_YELLOW}[vite-env] DEPRECATION WARNING${RESET}`),
|
|
50
|
+
boxLine(""),
|
|
51
|
+
boxLine(`virtual:env/server was imported from the "${fail.envName}"`),
|
|
52
|
+
boxLine("environment. This will be a hard build error in 1.0.0."),
|
|
53
|
+
boxLine(""),
|
|
54
|
+
boxLine("To enforce now: onClientAccessOfServerModule: 'error'", `To enforce now: ${CYAN}onClientAccessOfServerModule: 'error'${RESET}`),
|
|
55
|
+
boxLine("To silence: onClientAccessOfServerModule: 'stub'", `To silence: ${CYAN}onClientAccessOfServerModule: 'stub'${RESET}`),
|
|
56
|
+
boxLine(""),
|
|
57
|
+
boxLine(`Found in: ${importerDisplay}`),
|
|
58
|
+
boxBottom()
|
|
59
|
+
].join("\n");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Renders the plain-text hard error message thrown in 'error' mode.
|
|
63
|
+
* No ANSI — thrown as an Error message, not printed via logger.
|
|
64
|
+
*/
|
|
65
|
+
function formatHardError(fail) {
|
|
66
|
+
const importerLine = fail.importer ?? "(unknown)";
|
|
67
|
+
return [
|
|
68
|
+
`[vite-env] virtual:env/server is not available in the "${fail.envName}" environment.`,
|
|
69
|
+
``,
|
|
70
|
+
` Server-only modules cannot be imported from client code.`,
|
|
71
|
+
` Add this environment to serverEnvironments if intentional:`,
|
|
72
|
+
``,
|
|
73
|
+
` ViteEnv({ serverEnvironments: ['ssr', '${fail.envName}'] })`,
|
|
74
|
+
``,
|
|
75
|
+
` Or change enforcement:`,
|
|
76
|
+
``,
|
|
77
|
+
` ViteEnv({ onClientAccessOfServerModule: 'stub' })`,
|
|
78
|
+
``,
|
|
79
|
+
` Imported from: ${importerLine}`
|
|
80
|
+
].join("\n");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Renders a single ANSI-free log entry for vite-env-warnings.log.
|
|
84
|
+
* Uses the same (unknown) convention as formatGuardWarning for missing importers.
|
|
85
|
+
*/
|
|
86
|
+
function formatGuardLogEntry(fail, timestamp) {
|
|
87
|
+
const importerLine = fail.importer ?? "(unknown)";
|
|
88
|
+
return [`[${timestamp}] virtual:env/server accessed from "${fail.envName}" environment`, ` Importer: ${importerLine}`].join("\n");
|
|
89
|
+
}
|
|
13
90
|
//#endregion
|
|
91
|
+
exports.IMPORTER_MAX_LEN = IMPORTER_MAX_LEN;
|
|
92
|
+
exports.formatGuardLogEntry = formatGuardLogEntry;
|
|
93
|
+
exports.formatGuardWarning = formatGuardWarning;
|
|
94
|
+
exports.formatHardError = formatHardError;
|
|
14
95
|
exports.formatStandardSchemaError = formatStandardSchemaError;
|
|
15
96
|
exports.formatZodError = formatZodError;
|
|
97
|
+
exports.truncateImporter = truncateImporter;
|
|
16
98
|
|
|
17
99
|
//# sourceMappingURL=format.cjs.map
|
package/dist/format.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { core } from 'zod'\nimport type { StandardValidationIssue } from './types'\n\nexport function formatZodError(issues: core.$ZodIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n\nexport function formatStandardSchemaError(issues: StandardValidationIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0\n ? issue.path.map(seg => typeof seg === 'object' && seg !== null && 'key' in seg ? String(seg.key) : String(seg)).join('.')\n : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { core } from 'zod'\nimport type { GuardFail } from './guard'\nimport type { StandardValidationIssue } from './types'\n\nexport function formatZodError(issues: core.$ZodIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n\nexport function formatStandardSchemaError(issues: StandardValidationIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0\n ? issue.path.map(seg => typeof seg === 'object' && seg !== null && 'key' in seg ? String(seg.key) : String(seg)).join('.')\n : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n\nconst YELLOW = '\\x1B[33m'\nconst BOLD_YELLOW = '\\x1B[1;33m'\nconst CYAN = '\\x1B[36m'\nconst RESET = '\\x1B[0m'\n\nconst BOX_WIDTH = 66\n// 1 left border + 2 left spaces + content + 1 right border = BOX_WIDTH\n// content area = BOX_WIDTH - 4\nconst CONTENT_AREA = BOX_WIDTH - 4\n\n/** Maximum visible character length for an importer path inside the warning box. */\nexport const IMPORTER_MAX_LEN = CONTENT_AREA - 'Found in: '.length\n\n/**\n * Truncates an importer path to fit within maxLen visible characters.\n * Adds a leading '…' when truncation occurs so the rightmost (most specific) part is preserved.\n */\nexport function truncateImporter(importerPath: string, maxLen: number): string {\n if (importerPath.length <= maxLen)\n return importerPath\n return `\\u2026${importerPath.slice(-(maxLen - 1))}`\n}\n\nfunction boxTop(): string {\n return `${YELLOW}\\u250C${'─'.repeat(BOX_WIDTH - 2)}\\u2510${RESET}`\n}\n\nfunction boxBottom(): string {\n return `${YELLOW}\\u2514${'─'.repeat(BOX_WIDTH - 2)}\\u2518${RESET}`\n}\n\nfunction boxLine(visibleText: string, renderedText = visibleText): string {\n const rightPad = ' '.repeat(Math.max(0, CONTENT_AREA - visibleText.length))\n return `${YELLOW}\\u2502${RESET} ${renderedText}${rightPad}${YELLOW}\\u2502${RESET}`\n}\n\n/**\n * Renders the colored terminal warning box for 'warn' mode.\n * Every line is exactly 66 characters wide (visible chars, excluding ANSI codes),\n * assuming Vite environment names are short (≤19 chars). Longer names will extend\n * the line beyond 66 chars without crashing — Math.max(0) prevents negative padding.\n * Use in logger.warn() calls — not in log files.\n */\nexport function formatGuardWarning(fail: GuardFail): string {\n const importerDisplay = fail.importer === undefined\n ? '(unknown)'\n : truncateImporter(fail.importer, IMPORTER_MAX_LEN)\n\n const warnTitle = 'To enforce now: onClientAccessOfServerModule: \\'error\\''\n const stubTitle = 'To silence: onClientAccessOfServerModule: \\'stub\\''\n\n return [\n boxTop(),\n boxLine(\n '[vite-env] DEPRECATION WARNING',\n `${BOLD_YELLOW}[vite-env] DEPRECATION WARNING${RESET}`,\n ),\n boxLine(''),\n boxLine(`virtual:env/server was imported from the \"${fail.envName}\"`),\n boxLine('environment. This will be a hard build error in 1.0.0.'),\n boxLine(''),\n boxLine(warnTitle, `To enforce now: ${CYAN}onClientAccessOfServerModule: 'error'${RESET}`),\n boxLine(stubTitle, `To silence: ${CYAN}onClientAccessOfServerModule: 'stub'${RESET}`),\n boxLine(''),\n boxLine(`Found in: ${importerDisplay}`),\n boxBottom(),\n ].join('\\n')\n}\n\n/**\n * Renders the plain-text hard error message thrown in 'error' mode.\n * No ANSI — thrown as an Error message, not printed via logger.\n */\nexport function formatHardError(fail: GuardFail): string {\n const importerLine = fail.importer ?? '(unknown)'\n return [\n `[vite-env] virtual:env/server is not available in the \"${fail.envName}\" environment.`,\n ``,\n ` Server-only modules cannot be imported from client code.`,\n ` Add this environment to serverEnvironments if intentional:`,\n ``,\n ` ViteEnv({ serverEnvironments: ['ssr', '${fail.envName}'] })`,\n ``,\n ` Or change enforcement:`,\n ``,\n ` ViteEnv({ onClientAccessOfServerModule: 'stub' })`,\n ``,\n ` Imported from: ${importerLine}`,\n ].join('\\n')\n}\n\n/**\n * Renders a single ANSI-free log entry for vite-env-warnings.log.\n * Uses the same (unknown) convention as formatGuardWarning for missing importers.\n */\nexport function formatGuardLogEntry(fail: GuardFail, timestamp: string): string {\n const importerLine = fail.importer ?? '(unknown)'\n return [\n `[${timestamp}] virtual:env/server accessed from \"${fail.envName}\" environment`,\n ` Importer: ${importerLine}`,\n ].join('\\n')\n}\n"],"mappings":";;AAIA,SAAgB,eAAe,QAAkC;AAC/D,QAAO,OACJ,KAAK,UAAU;AAEd,SAAO,uBADM,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,UAC1B,OAAO,GAAG,CAAC,GAAG,MAAM;GACtD,CACD,KAAK,KAAK;;AAGf,SAAgB,0BAA0B,QAA2C;AACnF,QAAO,OACJ,KAAK,UAAU;AAId,SAAO,uBAHM,MAAM,KAAK,SAAS,IAC7B,MAAM,KAAK,KAAI,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,SAAS,MAAM,OAAO,IAAI,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,KAAK,IAAI,GACxH,UAC8B,OAAO,GAAG,CAAC,GAAG,MAAM;GACtD,CACD,KAAK,KAAK;;AAGf,MAAM,SAAS;AACf,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,QAAQ;AAEd,MAAM,YAAY;AAGlB,MAAM,eAAe,YAAY;;AAGjC,MAAa,mBAAmB,eAAe;;;;;AAM/C,SAAgB,iBAAiB,cAAsB,QAAwB;AAC7E,KAAI,aAAa,UAAU,OACzB,QAAO;AACT,QAAO,SAAS,aAAa,MAAM,EAAE,SAAS,GAAG;;AAGnD,SAAS,SAAiB;AACxB,QAAO,GAAG,OAAO,QAAQ,IAAI,OAAO,YAAY,EAAE,CAAC,QAAQ;;AAG7D,SAAS,YAAoB;AAC3B,QAAO,GAAG,OAAO,QAAQ,IAAI,OAAO,YAAY,EAAE,CAAC,QAAQ;;AAG7D,SAAS,QAAQ,aAAqB,eAAe,aAAqB;AAExE,QAAO,GAAG,OAAO,QAAQ,MAAM,IAAI,eADlB,IAAI,OAAO,KAAK,IAAI,GAAG,eAAe,YAAY,OAAO,CAAC,GACd,OAAO,QAAQ;;;;;;;;;AAU9E,SAAgB,mBAAmB,MAAyB;CAC1D,MAAM,kBAAkB,KAAK,aAAa,KAAA,IACtC,cACA,iBAAiB,KAAK,UAAA,GAA2B;AAKrD,QAAO;EACL,QAAQ;EACR,QACE,kCACA,GAAG,YAAY,gCAAgC,QAChD;EACD,QAAQ,GAAG;EACX,QAAQ,6CAA6C,KAAK,QAAQ,GAAG;EACrE,QAAQ,yDAAyD;EACjE,QAAQ,GAAG;EACX,QAbgB,0DAaG,oBAAoB,KAAK,uCAAuC,QAAQ;EAC3F,QAbgB,yDAaG,oBAAoB,KAAK,sCAAsC,QAAQ;EAC1F,QAAQ,GAAG;EACX,QAAQ,aAAa,kBAAkB;EACvC,WAAW;EACZ,CAAC,KAAK,KAAK;;;;;;AAOd,SAAgB,gBAAgB,MAAyB;CACvD,MAAM,eAAe,KAAK,YAAY;AACtC,QAAO;EACL,0DAA0D,KAAK,QAAQ;EACvE;EACA;EACA;EACA;EACA,8CAA8C,KAAK,QAAQ;EAC3D;EACA;EACA;EACA;EACA;EACA,oBAAoB;EACrB,CAAC,KAAK,KAAK;;;;;;AAOd,SAAgB,oBAAoB,MAAiB,WAA2B;CAC9E,MAAM,eAAe,KAAK,YAAY;AACtC,QAAO,CACL,IAAI,UAAU,sCAAsC,KAAK,QAAQ,gBACjE,eAAe,eAChB,CAAC,KAAK,KAAK"}
|
package/dist/format.d.cts
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as StandardValidationIssue } from "./types-Bl3YdPck.cjs";
|
|
2
2
|
import { core } from "zod";
|
|
3
3
|
|
|
4
|
+
//#region src/guard.d.ts
|
|
5
|
+
type GuardMode = 'error' | 'stub' | 'warn';
|
|
6
|
+
type GuardResult = {
|
|
7
|
+
allowed: true;
|
|
8
|
+
} | {
|
|
9
|
+
allowed: false;
|
|
10
|
+
mode: GuardMode;
|
|
11
|
+
envName: string;
|
|
12
|
+
importer: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
type GuardFail = Extract<GuardResult, {
|
|
15
|
+
allowed: false;
|
|
16
|
+
}>;
|
|
17
|
+
//#endregion
|
|
4
18
|
//#region src/format.d.ts
|
|
5
19
|
declare function formatZodError(issues: core.$ZodIssue[]): string;
|
|
6
20
|
declare function formatStandardSchemaError(issues: StandardValidationIssue[]): string;
|
|
21
|
+
/** Maximum visible character length for an importer path inside the warning box. */
|
|
22
|
+
declare const IMPORTER_MAX_LEN: number;
|
|
23
|
+
/**
|
|
24
|
+
* Truncates an importer path to fit within maxLen visible characters.
|
|
25
|
+
* Adds a leading '…' when truncation occurs so the rightmost (most specific) part is preserved.
|
|
26
|
+
*/
|
|
27
|
+
declare function truncateImporter(importerPath: string, maxLen: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Renders the colored terminal warning box for 'warn' mode.
|
|
30
|
+
* Every line is exactly 66 characters wide (visible chars, excluding ANSI codes),
|
|
31
|
+
* assuming Vite environment names are short (≤19 chars). Longer names will extend
|
|
32
|
+
* the line beyond 66 chars without crashing — Math.max(0) prevents negative padding.
|
|
33
|
+
* Use in logger.warn() calls — not in log files.
|
|
34
|
+
*/
|
|
35
|
+
declare function formatGuardWarning(fail: GuardFail): string;
|
|
36
|
+
/**
|
|
37
|
+
* Renders the plain-text hard error message thrown in 'error' mode.
|
|
38
|
+
* No ANSI — thrown as an Error message, not printed via logger.
|
|
39
|
+
*/
|
|
40
|
+
declare function formatHardError(fail: GuardFail): string;
|
|
41
|
+
/**
|
|
42
|
+
* Renders a single ANSI-free log entry for vite-env-warnings.log.
|
|
43
|
+
* Uses the same (unknown) convention as formatGuardWarning for missing importers.
|
|
44
|
+
*/
|
|
45
|
+
declare function formatGuardLogEntry(fail: GuardFail, timestamp: string): string;
|
|
7
46
|
//#endregion
|
|
8
|
-
export { formatStandardSchemaError, formatZodError };
|
|
47
|
+
export { IMPORTER_MAX_LEN, formatGuardLogEntry, formatGuardWarning, formatHardError, formatStandardSchemaError, formatZodError, truncateImporter };
|
|
9
48
|
//# sourceMappingURL=format.d.cts.map
|
package/dist/format.d.mts
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as StandardValidationIssue } from "./types-CUF5RARc.mjs";
|
|
2
2
|
import { core } from "zod";
|
|
3
3
|
|
|
4
|
+
//#region src/guard.d.ts
|
|
5
|
+
type GuardMode = 'error' | 'stub' | 'warn';
|
|
6
|
+
type GuardResult = {
|
|
7
|
+
allowed: true;
|
|
8
|
+
} | {
|
|
9
|
+
allowed: false;
|
|
10
|
+
mode: GuardMode;
|
|
11
|
+
envName: string;
|
|
12
|
+
importer: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
type GuardFail = Extract<GuardResult, {
|
|
15
|
+
allowed: false;
|
|
16
|
+
}>;
|
|
17
|
+
//#endregion
|
|
4
18
|
//#region src/format.d.ts
|
|
5
19
|
declare function formatZodError(issues: core.$ZodIssue[]): string;
|
|
6
20
|
declare function formatStandardSchemaError(issues: StandardValidationIssue[]): string;
|
|
21
|
+
/** Maximum visible character length for an importer path inside the warning box. */
|
|
22
|
+
declare const IMPORTER_MAX_LEN: number;
|
|
23
|
+
/**
|
|
24
|
+
* Truncates an importer path to fit within maxLen visible characters.
|
|
25
|
+
* Adds a leading '…' when truncation occurs so the rightmost (most specific) part is preserved.
|
|
26
|
+
*/
|
|
27
|
+
declare function truncateImporter(importerPath: string, maxLen: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Renders the colored terminal warning box for 'warn' mode.
|
|
30
|
+
* Every line is exactly 66 characters wide (visible chars, excluding ANSI codes),
|
|
31
|
+
* assuming Vite environment names are short (≤19 chars). Longer names will extend
|
|
32
|
+
* the line beyond 66 chars without crashing — Math.max(0) prevents negative padding.
|
|
33
|
+
* Use in logger.warn() calls — not in log files.
|
|
34
|
+
*/
|
|
35
|
+
declare function formatGuardWarning(fail: GuardFail): string;
|
|
36
|
+
/**
|
|
37
|
+
* Renders the plain-text hard error message thrown in 'error' mode.
|
|
38
|
+
* No ANSI — thrown as an Error message, not printed via logger.
|
|
39
|
+
*/
|
|
40
|
+
declare function formatHardError(fail: GuardFail): string;
|
|
41
|
+
/**
|
|
42
|
+
* Renders a single ANSI-free log entry for vite-env-warnings.log.
|
|
43
|
+
* Uses the same (unknown) convention as formatGuardWarning for missing importers.
|
|
44
|
+
*/
|
|
45
|
+
declare function formatGuardLogEntry(fail: GuardFail, timestamp: string): string;
|
|
7
46
|
//#endregion
|
|
8
|
-
export { formatStandardSchemaError, formatZodError };
|
|
47
|
+
export { IMPORTER_MAX_LEN, formatGuardLogEntry, formatGuardWarning, formatHardError, formatStandardSchemaError, formatZodError, truncateImporter };
|
|
9
48
|
//# sourceMappingURL=format.d.mts.map
|
package/dist/format.mjs
CHANGED
|
@@ -9,7 +9,84 @@ function formatStandardSchemaError(issues) {
|
|
|
9
9
|
return ` \x1B[31m✗\x1B[0m ${(issue.path.length > 0 ? issue.path.map((seg) => typeof seg === "object" && seg !== null && "key" in seg ? String(seg.key) : String(seg)).join(".") : "(root)").padEnd(28)} ${issue.message}`;
|
|
10
10
|
}).join("\n");
|
|
11
11
|
}
|
|
12
|
+
const YELLOW = "\x1B[33m";
|
|
13
|
+
const BOLD_YELLOW = "\x1B[1;33m";
|
|
14
|
+
const CYAN = "\x1B[36m";
|
|
15
|
+
const RESET = "\x1B[0m";
|
|
16
|
+
const BOX_WIDTH = 66;
|
|
17
|
+
const CONTENT_AREA = BOX_WIDTH - 4;
|
|
18
|
+
/** Maximum visible character length for an importer path inside the warning box. */
|
|
19
|
+
const IMPORTER_MAX_LEN = CONTENT_AREA - 10;
|
|
20
|
+
/**
|
|
21
|
+
* Truncates an importer path to fit within maxLen visible characters.
|
|
22
|
+
* Adds a leading '…' when truncation occurs so the rightmost (most specific) part is preserved.
|
|
23
|
+
*/
|
|
24
|
+
function truncateImporter(importerPath, maxLen) {
|
|
25
|
+
if (importerPath.length <= maxLen) return importerPath;
|
|
26
|
+
return `\u2026${importerPath.slice(-(maxLen - 1))}`;
|
|
27
|
+
}
|
|
28
|
+
function boxTop() {
|
|
29
|
+
return `${YELLOW}\u250C${"─".repeat(BOX_WIDTH - 2)}\u2510${RESET}`;
|
|
30
|
+
}
|
|
31
|
+
function boxBottom() {
|
|
32
|
+
return `${YELLOW}\u2514${"─".repeat(BOX_WIDTH - 2)}\u2518${RESET}`;
|
|
33
|
+
}
|
|
34
|
+
function boxLine(visibleText, renderedText = visibleText) {
|
|
35
|
+
return `${YELLOW}\u2502${RESET} ${renderedText}${" ".repeat(Math.max(0, CONTENT_AREA - visibleText.length))}${YELLOW}\u2502${RESET}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders the colored terminal warning box for 'warn' mode.
|
|
39
|
+
* Every line is exactly 66 characters wide (visible chars, excluding ANSI codes),
|
|
40
|
+
* assuming Vite environment names are short (≤19 chars). Longer names will extend
|
|
41
|
+
* the line beyond 66 chars without crashing — Math.max(0) prevents negative padding.
|
|
42
|
+
* Use in logger.warn() calls — not in log files.
|
|
43
|
+
*/
|
|
44
|
+
function formatGuardWarning(fail) {
|
|
45
|
+
const importerDisplay = fail.importer === void 0 ? "(unknown)" : truncateImporter(fail.importer, 52);
|
|
46
|
+
return [
|
|
47
|
+
boxTop(),
|
|
48
|
+
boxLine("[vite-env] DEPRECATION WARNING", `${BOLD_YELLOW}[vite-env] DEPRECATION WARNING${RESET}`),
|
|
49
|
+
boxLine(""),
|
|
50
|
+
boxLine(`virtual:env/server was imported from the "${fail.envName}"`),
|
|
51
|
+
boxLine("environment. This will be a hard build error in 1.0.0."),
|
|
52
|
+
boxLine(""),
|
|
53
|
+
boxLine("To enforce now: onClientAccessOfServerModule: 'error'", `To enforce now: ${CYAN}onClientAccessOfServerModule: 'error'${RESET}`),
|
|
54
|
+
boxLine("To silence: onClientAccessOfServerModule: 'stub'", `To silence: ${CYAN}onClientAccessOfServerModule: 'stub'${RESET}`),
|
|
55
|
+
boxLine(""),
|
|
56
|
+
boxLine(`Found in: ${importerDisplay}`),
|
|
57
|
+
boxBottom()
|
|
58
|
+
].join("\n");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Renders the plain-text hard error message thrown in 'error' mode.
|
|
62
|
+
* No ANSI — thrown as an Error message, not printed via logger.
|
|
63
|
+
*/
|
|
64
|
+
function formatHardError(fail) {
|
|
65
|
+
const importerLine = fail.importer ?? "(unknown)";
|
|
66
|
+
return [
|
|
67
|
+
`[vite-env] virtual:env/server is not available in the "${fail.envName}" environment.`,
|
|
68
|
+
``,
|
|
69
|
+
` Server-only modules cannot be imported from client code.`,
|
|
70
|
+
` Add this environment to serverEnvironments if intentional:`,
|
|
71
|
+
``,
|
|
72
|
+
` ViteEnv({ serverEnvironments: ['ssr', '${fail.envName}'] })`,
|
|
73
|
+
``,
|
|
74
|
+
` Or change enforcement:`,
|
|
75
|
+
``,
|
|
76
|
+
` ViteEnv({ onClientAccessOfServerModule: 'stub' })`,
|
|
77
|
+
``,
|
|
78
|
+
` Imported from: ${importerLine}`
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Renders a single ANSI-free log entry for vite-env-warnings.log.
|
|
83
|
+
* Uses the same (unknown) convention as formatGuardWarning for missing importers.
|
|
84
|
+
*/
|
|
85
|
+
function formatGuardLogEntry(fail, timestamp) {
|
|
86
|
+
const importerLine = fail.importer ?? "(unknown)";
|
|
87
|
+
return [`[${timestamp}] virtual:env/server accessed from "${fail.envName}" environment`, ` Importer: ${importerLine}`].join("\n");
|
|
88
|
+
}
|
|
12
89
|
//#endregion
|
|
13
|
-
export { formatStandardSchemaError, formatZodError };
|
|
90
|
+
export { IMPORTER_MAX_LEN, formatGuardLogEntry, formatGuardWarning, formatHardError, formatStandardSchemaError, formatZodError, truncateImporter };
|
|
14
91
|
|
|
15
92
|
//# sourceMappingURL=format.mjs.map
|
package/dist/format.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.mjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { core } from 'zod'\nimport type { StandardValidationIssue } from './types'\n\nexport function formatZodError(issues: core.$ZodIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n\nexport function formatStandardSchemaError(issues: StandardValidationIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0\n ? issue.path.map(seg => typeof seg === 'object' && seg !== null && 'key' in seg ? String(seg.key) : String(seg)).join('.')\n : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"format.mjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { core } from 'zod'\nimport type { GuardFail } from './guard'\nimport type { StandardValidationIssue } from './types'\n\nexport function formatZodError(issues: core.$ZodIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n\nexport function formatStandardSchemaError(issues: StandardValidationIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0\n ? issue.path.map(seg => typeof seg === 'object' && seg !== null && 'key' in seg ? String(seg.key) : String(seg)).join('.')\n : '(root)'\n return ` \\x1B[31m✗\\x1B[0m ${path.padEnd(28)} ${issue.message}`\n })\n .join('\\n')\n}\n\nconst YELLOW = '\\x1B[33m'\nconst BOLD_YELLOW = '\\x1B[1;33m'\nconst CYAN = '\\x1B[36m'\nconst RESET = '\\x1B[0m'\n\nconst BOX_WIDTH = 66\n// 1 left border + 2 left spaces + content + 1 right border = BOX_WIDTH\n// content area = BOX_WIDTH - 4\nconst CONTENT_AREA = BOX_WIDTH - 4\n\n/** Maximum visible character length for an importer path inside the warning box. */\nexport const IMPORTER_MAX_LEN = CONTENT_AREA - 'Found in: '.length\n\n/**\n * Truncates an importer path to fit within maxLen visible characters.\n * Adds a leading '…' when truncation occurs so the rightmost (most specific) part is preserved.\n */\nexport function truncateImporter(importerPath: string, maxLen: number): string {\n if (importerPath.length <= maxLen)\n return importerPath\n return `\\u2026${importerPath.slice(-(maxLen - 1))}`\n}\n\nfunction boxTop(): string {\n return `${YELLOW}\\u250C${'─'.repeat(BOX_WIDTH - 2)}\\u2510${RESET}`\n}\n\nfunction boxBottom(): string {\n return `${YELLOW}\\u2514${'─'.repeat(BOX_WIDTH - 2)}\\u2518${RESET}`\n}\n\nfunction boxLine(visibleText: string, renderedText = visibleText): string {\n const rightPad = ' '.repeat(Math.max(0, CONTENT_AREA - visibleText.length))\n return `${YELLOW}\\u2502${RESET} ${renderedText}${rightPad}${YELLOW}\\u2502${RESET}`\n}\n\n/**\n * Renders the colored terminal warning box for 'warn' mode.\n * Every line is exactly 66 characters wide (visible chars, excluding ANSI codes),\n * assuming Vite environment names are short (≤19 chars). Longer names will extend\n * the line beyond 66 chars without crashing — Math.max(0) prevents negative padding.\n * Use in logger.warn() calls — not in log files.\n */\nexport function formatGuardWarning(fail: GuardFail): string {\n const importerDisplay = fail.importer === undefined\n ? '(unknown)'\n : truncateImporter(fail.importer, IMPORTER_MAX_LEN)\n\n const warnTitle = 'To enforce now: onClientAccessOfServerModule: \\'error\\''\n const stubTitle = 'To silence: onClientAccessOfServerModule: \\'stub\\''\n\n return [\n boxTop(),\n boxLine(\n '[vite-env] DEPRECATION WARNING',\n `${BOLD_YELLOW}[vite-env] DEPRECATION WARNING${RESET}`,\n ),\n boxLine(''),\n boxLine(`virtual:env/server was imported from the \"${fail.envName}\"`),\n boxLine('environment. This will be a hard build error in 1.0.0.'),\n boxLine(''),\n boxLine(warnTitle, `To enforce now: ${CYAN}onClientAccessOfServerModule: 'error'${RESET}`),\n boxLine(stubTitle, `To silence: ${CYAN}onClientAccessOfServerModule: 'stub'${RESET}`),\n boxLine(''),\n boxLine(`Found in: ${importerDisplay}`),\n boxBottom(),\n ].join('\\n')\n}\n\n/**\n * Renders the plain-text hard error message thrown in 'error' mode.\n * No ANSI — thrown as an Error message, not printed via logger.\n */\nexport function formatHardError(fail: GuardFail): string {\n const importerLine = fail.importer ?? '(unknown)'\n return [\n `[vite-env] virtual:env/server is not available in the \"${fail.envName}\" environment.`,\n ``,\n ` Server-only modules cannot be imported from client code.`,\n ` Add this environment to serverEnvironments if intentional:`,\n ``,\n ` ViteEnv({ serverEnvironments: ['ssr', '${fail.envName}'] })`,\n ``,\n ` Or change enforcement:`,\n ``,\n ` ViteEnv({ onClientAccessOfServerModule: 'stub' })`,\n ``,\n ` Imported from: ${importerLine}`,\n ].join('\\n')\n}\n\n/**\n * Renders a single ANSI-free log entry for vite-env-warnings.log.\n * Uses the same (unknown) convention as formatGuardWarning for missing importers.\n */\nexport function formatGuardLogEntry(fail: GuardFail, timestamp: string): string {\n const importerLine = fail.importer ?? '(unknown)'\n return [\n `[${timestamp}] virtual:env/server accessed from \"${fail.envName}\" environment`,\n ` Importer: ${importerLine}`,\n ].join('\\n')\n}\n"],"mappings":";AAIA,SAAgB,eAAe,QAAkC;AAC/D,QAAO,OACJ,KAAK,UAAU;AAEd,SAAO,uBADM,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,UAC1B,OAAO,GAAG,CAAC,GAAG,MAAM;GACtD,CACD,KAAK,KAAK;;AAGf,SAAgB,0BAA0B,QAA2C;AACnF,QAAO,OACJ,KAAK,UAAU;AAId,SAAO,uBAHM,MAAM,KAAK,SAAS,IAC7B,MAAM,KAAK,KAAI,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,SAAS,MAAM,OAAO,IAAI,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,KAAK,IAAI,GACxH,UAC8B,OAAO,GAAG,CAAC,GAAG,MAAM;GACtD,CACD,KAAK,KAAK;;AAGf,MAAM,SAAS;AACf,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,QAAQ;AAEd,MAAM,YAAY;AAGlB,MAAM,eAAe,YAAY;;AAGjC,MAAa,mBAAmB,eAAe;;;;;AAM/C,SAAgB,iBAAiB,cAAsB,QAAwB;AAC7E,KAAI,aAAa,UAAU,OACzB,QAAO;AACT,QAAO,SAAS,aAAa,MAAM,EAAE,SAAS,GAAG;;AAGnD,SAAS,SAAiB;AACxB,QAAO,GAAG,OAAO,QAAQ,IAAI,OAAO,YAAY,EAAE,CAAC,QAAQ;;AAG7D,SAAS,YAAoB;AAC3B,QAAO,GAAG,OAAO,QAAQ,IAAI,OAAO,YAAY,EAAE,CAAC,QAAQ;;AAG7D,SAAS,QAAQ,aAAqB,eAAe,aAAqB;AAExE,QAAO,GAAG,OAAO,QAAQ,MAAM,IAAI,eADlB,IAAI,OAAO,KAAK,IAAI,GAAG,eAAe,YAAY,OAAO,CAAC,GACd,OAAO,QAAQ;;;;;;;;;AAU9E,SAAgB,mBAAmB,MAAyB;CAC1D,MAAM,kBAAkB,KAAK,aAAa,KAAA,IACtC,cACA,iBAAiB,KAAK,UAAA,GAA2B;AAKrD,QAAO;EACL,QAAQ;EACR,QACE,kCACA,GAAG,YAAY,gCAAgC,QAChD;EACD,QAAQ,GAAG;EACX,QAAQ,6CAA6C,KAAK,QAAQ,GAAG;EACrE,QAAQ,yDAAyD;EACjE,QAAQ,GAAG;EACX,QAbgB,0DAaG,oBAAoB,KAAK,uCAAuC,QAAQ;EAC3F,QAbgB,yDAaG,oBAAoB,KAAK,sCAAsC,QAAQ;EAC1F,QAAQ,GAAG;EACX,QAAQ,aAAa,kBAAkB;EACvC,WAAW;EACZ,CAAC,KAAK,KAAK;;;;;;AAOd,SAAgB,gBAAgB,MAAyB;CACvD,MAAM,eAAe,KAAK,YAAY;AACtC,QAAO;EACL,0DAA0D,KAAK,QAAQ;EACvE;EACA;EACA;EACA;EACA,8CAA8C,KAAK,QAAQ;EAC3D;EACA;EACA;EACA;EACA;EACA,oBAAoB;EACrB,CAAC,KAAK,KAAK;;;;;;AAOd,SAAgB,oBAAoB,MAAiB,WAA2B;CAC9E,MAAM,eAAe,KAAK,YAAY;AACtC,QAAO,CACL,IAAI,UAAU,sCAAsC,KAAK,QAAQ,gBACjE,eAAe,eAChB,CAAC,KAAK,KAAK"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as InferServerEnv, i as InferClientEnv, n as EnvDefinition, o as StandardEnvDefinition, r as EnvPreset, t as AnyEnvDefinition } from "./types-Bl3YdPck.cjs";
|
|
2
2
|
import { defineEnv } from "./schema.cjs";
|
|
3
3
|
import { defineStandardEnv } from "./standard.cjs";
|
|
4
|
-
export { type AnyEnvDefinition, type EnvDefinition, type InferClientEnv, type InferServerEnv, type StandardEnvDefinition, defineEnv, defineStandardEnv };
|
|
4
|
+
export { type AnyEnvDefinition, type EnvDefinition, type EnvPreset, type InferClientEnv, type InferServerEnv, type StandardEnvDefinition, defineEnv, defineStandardEnv };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as InferServerEnv, i as InferClientEnv, n as EnvDefinition, o as StandardEnvDefinition, r as EnvPreset, t as AnyEnvDefinition } from "./types-CUF5RARc.mjs";
|
|
2
2
|
import { defineEnv } from "./schema.mjs";
|
|
3
3
|
import { defineStandardEnv } from "./standard.mjs";
|
|
4
|
-
export { type AnyEnvDefinition, type EnvDefinition, type InferClientEnv, type InferServerEnv, type StandardEnvDefinition, defineEnv, defineStandardEnv };
|
|
4
|
+
export { type AnyEnvDefinition, type EnvDefinition, type EnvPreset, type InferClientEnv, type InferServerEnv, type StandardEnvDefinition, defineEnv, defineStandardEnv };
|
package/dist/leak.d.cts
CHANGED
package/dist/leak.d.mts
CHANGED
package/dist/plugin.cjs
CHANGED
|
@@ -7,7 +7,72 @@ let node_path = require("node:path");
|
|
|
7
7
|
node_path = require_dts.__toESM(node_path);
|
|
8
8
|
let node_process = require("node:process");
|
|
9
9
|
node_process = require_dts.__toESM(node_process);
|
|
10
|
+
let node_fs_promises = require("node:fs/promises");
|
|
11
|
+
node_fs_promises = require_dts.__toESM(node_fs_promises);
|
|
10
12
|
let vite = require("vite");
|
|
13
|
+
//#region src/guard.ts
|
|
14
|
+
/**
|
|
15
|
+
* Determines whether the given Vite environment is allowed to import virtual:env/server.
|
|
16
|
+
* Returns a GuardResult discriminated union — allowed or fail with context.
|
|
17
|
+
* When this.environment is undefined (should not occur with Vite ≥ 8), callers default
|
|
18
|
+
* envName to 'client' — failing closed (restrictive) is safer than failing open.
|
|
19
|
+
*/
|
|
20
|
+
function checkServerModuleAccess(envName, serverEnvironments, mode, importer) {
|
|
21
|
+
if (serverEnvironments.includes(envName)) return { allowed: true };
|
|
22
|
+
return {
|
|
23
|
+
allowed: false,
|
|
24
|
+
mode,
|
|
25
|
+
envName,
|
|
26
|
+
importer
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generates the stub virtual module returned when onClientAccessOfServerModule is 'stub'.
|
|
31
|
+
* The stub throws at runtime if executed — its message reflects that this import was
|
|
32
|
+
* expected to be unreachable and the assumption was wrong.
|
|
33
|
+
*/
|
|
34
|
+
function buildServerStubModule(envName) {
|
|
35
|
+
return {
|
|
36
|
+
moduleType: "js",
|
|
37
|
+
code: `// Auto-generated by @vite-env/core — server-only module stub
|
|
38
|
+
throw new Error(
|
|
39
|
+
'[vite-env] virtual:env/server was imported in the "${envName}" environment. ' +
|
|
40
|
+
'This module is server-only and was replaced with a stub. ' +
|
|
41
|
+
'To allow this environment: add it to serverEnvironments. ' +
|
|
42
|
+
'To suppress this stub: ensure this import never executes in the "${envName}" environment.'
|
|
43
|
+
);`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/log.ts
|
|
48
|
+
const LOG_HEADER = `# vite-env warnings — generated by @vite-env/core
|
|
49
|
+
# These warnings will become hard errors in 1.0.0.
|
|
50
|
+
# To enforce immediately: ViteEnv({ onClientAccessOfServerModule: 'error' })
|
|
51
|
+
# To acknowledge and suppress: ViteEnv({ onClientAccessOfServerModule: 'stub' })
|
|
52
|
+
# To allow specific environments: ViteEnv({ serverEnvironments: ['ssr', 'workerd'] })`;
|
|
53
|
+
/**
|
|
54
|
+
* Writes accumulated GuardFail entries to vite-env-warnings.log in the project root.
|
|
55
|
+
* Overwrites on each build — stale entries from previous builds must not persist.
|
|
56
|
+
* Called only in 'warn' mode from buildEnd, after verifying the build succeeded.
|
|
57
|
+
*/
|
|
58
|
+
async function writeWarningsLog(fails, root) {
|
|
59
|
+
const seen = /* @__PURE__ */ new Set();
|
|
60
|
+
const unique = fails.filter((fail) => {
|
|
61
|
+
const key = `${fail.envName}::${fail.importer ?? ""}`;
|
|
62
|
+
if (seen.has(key)) return false;
|
|
63
|
+
seen.add(key);
|
|
64
|
+
return true;
|
|
65
|
+
});
|
|
66
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
67
|
+
const content = `${LOG_HEADER}\n\n${unique.map((fail) => require_format.formatGuardLogEntry(fail, timestamp)).join("\n\n")}\n`;
|
|
68
|
+
const filePath = node_path.default.join(root, "vite-env-warnings.log");
|
|
69
|
+
try {
|
|
70
|
+
await node_fs_promises.default.writeFile(filePath, content, "utf-8");
|
|
71
|
+
} catch (e) {
|
|
72
|
+
throw new Error(`[vite-env] Failed to write vite-env-warnings.log to ${root}. Check file permissions.`, { cause: e });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
11
76
|
//#region src/sources.ts
|
|
12
77
|
/**
|
|
13
78
|
* Merge priority (highest → lowest):
|
|
@@ -71,6 +136,10 @@ function ViteEnv(options = {}) {
|
|
|
71
136
|
let resolvedConfig;
|
|
72
137
|
let envDefinition;
|
|
73
138
|
let lastValidated = {};
|
|
139
|
+
let serverModuleGuardFails = [];
|
|
140
|
+
let didSetExitCode = false;
|
|
141
|
+
const serverEnvs = options.serverEnvironments ?? ["ssr"];
|
|
142
|
+
const guardMode = options.onClientAccessOfServerModule ?? "warn";
|
|
74
143
|
return {
|
|
75
144
|
name: "vite-env",
|
|
76
145
|
enforce: "pre",
|
|
@@ -84,6 +153,11 @@ function ViteEnv(options = {}) {
|
|
|
84
153
|
}
|
|
85
154
|
},
|
|
86
155
|
async buildStart() {
|
|
156
|
+
serverModuleGuardFails = [];
|
|
157
|
+
if (didSetExitCode) {
|
|
158
|
+
node_process.default.exitCode = 0;
|
|
159
|
+
didSetExitCode = false;
|
|
160
|
+
}
|
|
87
161
|
const rawEnv = await loadEnvSources(resolvedConfig);
|
|
88
162
|
const result = await validateAndFormat(envDefinition, rawEnv);
|
|
89
163
|
if ("error" in result) throw new Error(`[vite-env] Environment validation failed:\n\n${result.error}`);
|
|
@@ -96,13 +170,35 @@ function ViteEnv(options = {}) {
|
|
|
96
170
|
const count = Object.keys(lastValidated).length;
|
|
97
171
|
resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m ${count} variables validated`);
|
|
98
172
|
},
|
|
99
|
-
resolveId(
|
|
100
|
-
if (
|
|
101
|
-
if (
|
|
173
|
+
resolveId(source, importer) {
|
|
174
|
+
if (source === "virtual:env/client") return "\0virtual:env/client";
|
|
175
|
+
if (source === "virtual:env/server") {
|
|
176
|
+
const result = checkServerModuleAccess(this.environment?.name ?? "client", serverEnvs, guardMode, importer);
|
|
177
|
+
if (!result.allowed) serverModuleGuardFails.push(result);
|
|
178
|
+
return "\0virtual:env/server";
|
|
179
|
+
}
|
|
102
180
|
},
|
|
103
181
|
load(id) {
|
|
104
182
|
if (id === "\0virtual:env/client") return buildClientModule(envDefinition, lastValidated);
|
|
105
|
-
if (id === "\0virtual:env/server")
|
|
183
|
+
if (id === "\0virtual:env/server") {
|
|
184
|
+
const envName = this.environment?.name ?? "client";
|
|
185
|
+
const envFails = serverModuleGuardFails.filter((f) => f.envName === envName);
|
|
186
|
+
if (envFails.length > 0) {
|
|
187
|
+
const latest = envFails.at(-1);
|
|
188
|
+
if (latest.mode === "error") throw new Error(require_format.formatHardError(latest));
|
|
189
|
+
if (latest.mode === "stub") return buildServerStubModule(envName);
|
|
190
|
+
resolvedConfig.logger.warn(`\n${require_format.formatGuardWarning(latest)}`);
|
|
191
|
+
}
|
|
192
|
+
return buildServerModule(envDefinition, lastValidated);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
async buildEnd(error) {
|
|
196
|
+
if (error) return;
|
|
197
|
+
if (serverModuleGuardFails.length === 0) return;
|
|
198
|
+
if (guardMode !== "warn") return;
|
|
199
|
+
await writeWarningsLog(serverModuleGuardFails, resolvedConfig.root);
|
|
200
|
+
node_process.default.exitCode = 1;
|
|
201
|
+
didSetExitCode = true;
|
|
106
202
|
},
|
|
107
203
|
generateBundle(_options, bundle) {
|
|
108
204
|
if (resolvedConfig.build.ssr) return;
|
|
@@ -135,6 +231,7 @@ function ViteEnv(options = {}) {
|
|
|
135
231
|
if (clientMod) server.moduleGraph.invalidateModule(clientMod);
|
|
136
232
|
if (serverMod) server.moduleGraph.invalidateModule(serverMod);
|
|
137
233
|
if (clientMod || serverMod) {
|
|
234
|
+
serverModuleGuardFails = [];
|
|
138
235
|
server.hot.send({ type: "full-reload" });
|
|
139
236
|
resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidated`);
|
|
140
237
|
}
|
package/dist/plugin.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs","names":["process","isStandardEnvDefinition","validateStandardEnv","formatStandardSchemaError","path","loadEnvConfig","generateStandardDts","detectServerLeak"],"sources":["../src/sources.ts","../src/virtual.ts","../src/plugin.ts"],"sourcesContent":["// @env node\nimport type { ResolvedConfig } from 'vite'\nimport process from 'node:process'\nimport { loadEnv } from 'vite'\n\n/**\n * Merge priority (highest → lowest):\n * 1. process.env (CI pipeline secrets win)\n * 2. .env.[mode].local\n * 3. .env.[mode]\n * 4. .env.local\n * 5. .env\n *\n * Prefix '' = load everything, schema decides what's valid.\n */\nexport async function loadEnvSources(\n config: ResolvedConfig,\n): Promise<Record<string, string>> {\n const fileEnv = loadEnv(\n config.mode,\n config.envDir || config.root,\n '', // no prefix filter — schema is the filter\n )\n\n return {\n ...fileEnv,\n ...filterStrings(process.env),\n }\n}\n\nfunction filterStrings(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n}\n","import type { AnyEnvDefinition } from './types'\n\nexport function buildClientModule(\n def: AnyEnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n const clientKeys = new Set(Object.keys(def.client ?? {}))\n\n const clientData = Object.fromEntries(\n Object.entries(data).filter(([k]) => clientKeys.has(k)),\n )\n\n return {\n moduleType: 'js', // Required: Vite 8 / Rolldown explicit moduleType\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(clientData, null, 2)});\nexport default env;`,\n }\n}\n\nexport function buildServerModule(\n _def: AnyEnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(data, null, 2)});\nexport default env;`,\n }\n}\n","// @env node\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { AnyEnvDefinition } from './types'\nimport path from 'node:path'\nimport { loadEnvConfig } from './config'\nimport { generateStandardDts } from './dts'\nimport { formatStandardSchemaError } from './format'\nimport { detectServerLeak } from './leak'\nimport { loadEnvSources } from './sources'\nimport { isStandardEnvDefinition, validateStandardEnv } from './standard'\nimport { buildClientModule, buildServerModule } from './virtual'\n\nexport interface ViteEnvOptions {\n /**\n * Path to env definition file.\n * @default './env.ts' (resolved from project root)\n */\n configFile?: string\n}\n\n/**\n * Validates environment variables against the definition.\n * Routes to Zod or Standard Schema path based on definition type.\n * Zod modules are loaded dynamically to avoid requiring zod for Standard Schema users.\n */\nasync function validateAndFormat(\n def: AnyEnvDefinition,\n rawEnv: Record<string, string>,\n): Promise<{ data: Record<string, unknown> } | { error: string }> {\n if (isStandardEnvDefinition(def)) {\n const result = await validateStandardEnv(def, rawEnv)\n if (!result.success) {\n return { error: formatStandardSchemaError(result.errors) }\n }\n return { data: result.data }\n }\n\n const { validateEnv } = await import('./schema')\n const { formatZodError } = await import('./format')\n const result = validateEnv(def, rawEnv)\n if (!result.success) {\n return { error: formatZodError(result.errors) }\n }\n return { data: result.data }\n}\n\nexport default function ViteEnv(options: ViteEnvOptions = {}): Plugin {\n let resolvedConfig: ResolvedConfig\n let envDefinition: AnyEnvDefinition\n let lastValidated: Record<string, unknown> = {}\n\n return {\n name: 'vite-env',\n enforce: 'pre',\n\n async configResolved(config) {\n resolvedConfig = config\n\n const configPath = path.resolve(\n config.root,\n options.configFile ?? 'env.ts',\n )\n\n try {\n envDefinition = await loadEnvConfig(configPath)\n }\n catch (e) {\n throw new Error(\n `[vite-env] Could not load env definition file at: ${configPath}\\n`\n + ` Create an env.ts file and export default defineEnv({ ... })`,\n { cause: e },\n )\n }\n },\n\n async buildStart() {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = await validateAndFormat(envDefinition, rawEnv)\n\n if ('error' in result) {\n throw new Error(\n `[vite-env] Environment validation failed:\\n\\n${result.error}`,\n )\n }\n\n lastValidated = result.data\n\n if (isStandardEnvDefinition(envDefinition)) {\n await generateStandardDts(envDefinition, resolvedConfig.root)\n }\n else {\n const { generateDts } = await import('./dts')\n await generateDts(envDefinition, resolvedConfig.root)\n }\n\n const count = Object.keys(lastValidated).length\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m ${count} variables validated`,\n )\n },\n\n resolveId(id) {\n if (id === 'virtual:env/client')\n return '\\0virtual:env/client'\n if (id === 'virtual:env/server')\n return '\\0virtual:env/server'\n },\n\n load(id) {\n if (id === '\\0virtual:env/client')\n return buildClientModule(envDefinition, lastValidated)\n if (id === '\\0virtual:env/server')\n return buildServerModule(envDefinition, lastValidated)\n },\n\n generateBundle(_options, bundle) {\n if (resolvedConfig.build.ssr)\n return\n\n const leaks = detectServerLeak(\n envDefinition,\n lastValidated,\n bundle as Record<string, { type: string, code?: string }>,\n (keys) => {\n resolvedConfig.logger.warn(\n ` \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Leak detection skipped ${keys.length} server variable(s) with values shorter than 8 chars: ${keys.join(', ')}`,\n )\n },\n )\n\n if (leaks.length > 0) {\n const details = leaks.map(l => ` ✗ ${l.key} found in ${l.chunk}`).join('\\n')\n throw new Error(\n `[vite-env] Server environment variables detected in client bundle!\\n\\n${details}\\n\\n These variables are marked as server-only and must never reach the browser.`,\n )\n }\n },\n\n configureServer(server) {\n const envDir = resolvedConfig.envDir || resolvedConfig.root\n server.watcher.add(path.join(envDir, '.env*'))\n\n let debounceTimer: ReturnType<typeof setTimeout>\n\n server.watcher.on('change', async (file) => {\n if (!path.basename(file).startsWith('.env'))\n return\n\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(async () => {\n try {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = await validateAndFormat(envDefinition, rawEnv)\n\n if ('error' in result) {\n resolvedConfig.logger.warn(\n `\\n \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidation failed:\\n${result.error}`,\n )\n return\n }\n\n lastValidated = result.data\n\n const clientMod = server.moduleGraph.getModuleById('\\0virtual:env/client')\n const serverMod = server.moduleGraph.getModuleById('\\0virtual:env/server')\n if (clientMod)\n server.moduleGraph.invalidateModule(clientMod)\n if (serverMod)\n server.moduleGraph.invalidateModule(serverMod)\n if (clientMod || serverMod) {\n server.hot.send({ type: 'full-reload' })\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidated`,\n )\n }\n }\n catch (e) {\n resolvedConfig.logger.error(\n `\\n \\x1B[31m✗\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Failed to reload env files: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n }, 150) // 150ms debounce\n })\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.cjs","names":["formatGuardLogEntry","path","fs","process","isStandardEnvDefinition","validateStandardEnv","formatStandardSchemaError","path","loadEnvConfig","generateStandardDts","formatHardError","formatGuardWarning","detectServerLeak"],"sources":["../src/guard.ts","../src/log.ts","../src/sources.ts","../src/virtual.ts","../src/plugin.ts"],"sourcesContent":["export type GuardMode = 'error' | 'stub' | 'warn'\n\nexport type GuardResult\n = | { allowed: true }\n | { allowed: false, mode: GuardMode, envName: string, importer: string | undefined }\n\nexport type GuardFail = Extract<GuardResult, { allowed: false }>\n\n/**\n * Determines whether the given Vite environment is allowed to import virtual:env/server.\n * Returns a GuardResult discriminated union — allowed or fail with context.\n * When this.environment is undefined (should not occur with Vite ≥ 8), callers default\n * envName to 'client' — failing closed (restrictive) is safer than failing open.\n */\nexport function checkServerModuleAccess(\n envName: string,\n serverEnvironments: string[],\n mode: GuardMode,\n importer: string | undefined,\n): GuardResult {\n if (serverEnvironments.includes(envName))\n return { allowed: true }\n return { allowed: false, mode, envName, importer }\n}\n\n/**\n * Generates the stub virtual module returned when onClientAccessOfServerModule is 'stub'.\n * The stub throws at runtime if executed — its message reflects that this import was\n * expected to be unreachable and the assumption was wrong.\n */\nexport function buildServerStubModule(envName: string): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — server-only module stub\nthrow new Error(\n '[vite-env] virtual:env/server was imported in the \"${envName}\" environment. ' +\n 'This module is server-only and was replaced with a stub. ' +\n 'To allow this environment: add it to serverEnvironments. ' +\n 'To suppress this stub: ensure this import never executes in the \"${envName}\" environment.'\n);`,\n }\n}\n","// @env node\nimport type { GuardFail } from './guard'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { formatGuardLogEntry } from './format'\n\nconst LOG_HEADER = `# vite-env warnings — generated by @vite-env/core\n# These warnings will become hard errors in 1.0.0.\n# To enforce immediately: ViteEnv({ onClientAccessOfServerModule: 'error' })\n# To acknowledge and suppress: ViteEnv({ onClientAccessOfServerModule: 'stub' })\n# To allow specific environments: ViteEnv({ serverEnvironments: ['ssr', 'workerd'] })`\n\n/**\n * Writes accumulated GuardFail entries to vite-env-warnings.log in the project root.\n * Overwrites on each build — stale entries from previous builds must not persist.\n * Called only in 'warn' mode from buildEnd, after verifying the build succeeded.\n */\nexport async function writeWarningsLog(fails: GuardFail[], root: string): Promise<void> {\n const seen = new Set<string>()\n const unique = fails.filter((fail) => {\n const key = `${fail.envName}::${fail.importer ?? ''}`\n if (seen.has(key))\n return false\n seen.add(key)\n return true\n })\n const timestamp = new Date().toISOString()\n const entries = unique.map(fail => formatGuardLogEntry(fail, timestamp)).join('\\n\\n')\n const content = `${LOG_HEADER}\\n\\n${entries}\\n`\n const filePath = path.join(root, 'vite-env-warnings.log')\n try {\n await fs.writeFile(filePath, content, 'utf-8')\n }\n catch (e) {\n throw new Error(\n `[vite-env] Failed to write vite-env-warnings.log to ${root}. Check file permissions.`,\n { cause: e },\n )\n }\n}\n","// @env node\nimport type { ResolvedConfig } from 'vite'\nimport process from 'node:process'\nimport { loadEnv } from 'vite'\n\n/**\n * Merge priority (highest → lowest):\n * 1. process.env (CI pipeline secrets win)\n * 2. .env.[mode].local\n * 3. .env.[mode]\n * 4. .env.local\n * 5. .env\n *\n * Prefix '' = load everything, schema decides what's valid.\n */\nexport async function loadEnvSources(\n config: ResolvedConfig,\n): Promise<Record<string, string>> {\n const fileEnv = loadEnv(\n config.mode,\n config.envDir || config.root,\n '', // no prefix filter — schema is the filter\n )\n\n return {\n ...fileEnv,\n ...filterStrings(process.env),\n }\n}\n\nfunction filterStrings(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n}\n","import type { AnyEnvDefinition } from './types'\n\nexport function buildClientModule(\n def: AnyEnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n const clientKeys = new Set(Object.keys(def.client ?? {}))\n\n const clientData = Object.fromEntries(\n Object.entries(data).filter(([k]) => clientKeys.has(k)),\n )\n\n return {\n moduleType: 'js', // Required: Vite 8 / Rolldown explicit moduleType\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(clientData, null, 2)});\nexport default env;`,\n }\n}\n\nexport function buildServerModule(\n _def: AnyEnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(data, null, 2)});\nexport default env;`,\n }\n}\n","// @env node\nimport type { Plugin, ResolvedConfig, Rollup } from 'vite'\nimport type { GuardFail } from './guard'\nimport type { AnyEnvDefinition } from './types'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { loadEnvConfig } from './config'\nimport { generateStandardDts } from './dts'\nimport { formatGuardWarning, formatHardError, formatStandardSchemaError } from './format'\nimport { buildServerStubModule, checkServerModuleAccess } from './guard'\nimport { detectServerLeak } from './leak'\nimport { writeWarningsLog } from './log'\nimport { loadEnvSources } from './sources'\nimport { isStandardEnvDefinition, validateStandardEnv } from './standard'\nimport { buildClientModule, buildServerModule } from './virtual'\n\nexport interface ViteEnvOptions {\n /**\n * Path to env definition file.\n * @default './env.ts' (resolved from project root)\n */\n configFile?: string\n\n /**\n * Vite 8 environment names that are allowed to import virtual:env/server.\n * Use this to allow edge runtimes (Cloudflare Workers → 'workerd', Deno Deploy → 'ssr').\n * @default ['ssr']\n */\n serverEnvironments?: string[]\n\n /**\n * Behavior when virtual:env/server is imported from a disallowed environment.\n *\n * - 'warn' — Deprecation warning printed to terminal + vite-env-warnings.log written.\n * Build succeeds but exits with code 1. Default in 0.x releases.\n * The default will change to 'error' in 1.0.0.\n *\n * - 'error' — Hard build error. No artifacts emitted.\n *\n * - 'stub' — Returns a module that throws at runtime if the import executes.\n * Use for testing environments (Vitest jsdom) or framework isomorphic files\n * where the import exists but the code path is never reached in a server context.\n *\n * @default 'warn'\n */\n onClientAccessOfServerModule?: 'error' | 'stub' | 'warn'\n}\n\n/**\n * Validates environment variables against the definition.\n * Routes to Zod or Standard Schema path based on definition type.\n * Zod modules are loaded dynamically to avoid requiring zod for Standard Schema users.\n */\nasync function validateAndFormat(\n def: AnyEnvDefinition,\n rawEnv: Record<string, string>,\n): Promise<{ data: Record<string, unknown> } | { error: string }> {\n if (isStandardEnvDefinition(def)) {\n const result = await validateStandardEnv(def, rawEnv)\n if (!result.success) {\n return { error: formatStandardSchemaError(result.errors) }\n }\n return { data: result.data }\n }\n\n const { validateEnv } = await import('./schema')\n const { formatZodError } = await import('./format')\n const result = validateEnv(def, rawEnv)\n if (!result.success) {\n return { error: formatZodError(result.errors) }\n }\n return { data: result.data }\n}\n\nexport default function ViteEnv(options: ViteEnvOptions = {}): Plugin {\n let resolvedConfig: ResolvedConfig\n let envDefinition: AnyEnvDefinition\n let lastValidated: Record<string, unknown> = {}\n let serverModuleGuardFails: GuardFail[] = []\n let didSetExitCode = false\n\n const serverEnvs = options.serverEnvironments ?? ['ssr']\n const guardMode = options.onClientAccessOfServerModule ?? 'warn'\n\n return {\n name: 'vite-env',\n enforce: 'pre',\n\n async configResolved(config) {\n resolvedConfig = config\n\n const configPath = path.resolve(\n config.root,\n options.configFile ?? 'env.ts',\n )\n\n try {\n envDefinition = await loadEnvConfig(configPath)\n }\n catch (e) {\n throw new Error(\n `[vite-env] Could not load env definition file at: ${configPath}\\n`\n + ` Create an env.ts file and export default defineEnv({ ... })`,\n { cause: e },\n )\n }\n },\n\n async buildStart() {\n serverModuleGuardFails = []\n if (didSetExitCode) {\n process.exitCode = 0\n didSetExitCode = false\n }\n\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = await validateAndFormat(envDefinition, rawEnv)\n\n if ('error' in result) {\n throw new Error(\n `[vite-env] Environment validation failed:\\n\\n${result.error}`,\n )\n }\n\n lastValidated = result.data\n\n if (isStandardEnvDefinition(envDefinition)) {\n await generateStandardDts(envDefinition, resolvedConfig.root)\n }\n else {\n const { generateDts } = await import('./dts')\n await generateDts(envDefinition, resolvedConfig.root)\n }\n\n const count = Object.keys(lastValidated).length\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m ${count} variables validated`,\n )\n },\n\n resolveId(this: Rollup.PluginContext, source, importer) {\n if (source === 'virtual:env/client')\n return '\\0virtual:env/client'\n if (source === 'virtual:env/server') {\n const envName = this.environment?.name ?? 'client'\n const result = checkServerModuleAccess(envName, serverEnvs, guardMode, importer)\n if (!result.allowed)\n serverModuleGuardFails.push(result)\n return '\\0virtual:env/server'\n }\n },\n\n load(this: Rollup.PluginContext, id) {\n if (id === '\\0virtual:env/client')\n return buildClientModule(envDefinition, lastValidated)\n if (id === '\\0virtual:env/server') {\n const envName = this.environment?.name ?? 'client'\n // Filter to fails from this environment only — other envs may have recorded fails for their own loads\n const envFails = serverModuleGuardFails.filter(f => f.envName === envName)\n if (envFails.length > 0) {\n // warn once per load cycle using the last recorded fail; unique importers are written to the log file\n const latest = envFails.at(-1)!\n if (latest.mode === 'error')\n throw new Error(formatHardError(latest))\n if (latest.mode === 'stub')\n return buildServerStubModule(envName)\n resolvedConfig.logger.warn(`\\n${formatGuardWarning(latest)}`)\n }\n return buildServerModule(envDefinition, lastValidated)\n }\n },\n\n async buildEnd(error) {\n if (error)\n return\n if (serverModuleGuardFails.length === 0)\n return\n if (guardMode !== 'warn')\n return\n await writeWarningsLog(serverModuleGuardFails, resolvedConfig.root)\n process.exitCode = 1\n didSetExitCode = true\n },\n\n generateBundle(_options, bundle) {\n if (resolvedConfig.build.ssr)\n return\n\n const leaks = detectServerLeak(\n envDefinition,\n lastValidated,\n bundle as Record<string, { type: string, code?: string }>,\n (keys) => {\n resolvedConfig.logger.warn(\n ` \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Leak detection skipped ${keys.length} server variable(s) with values shorter than 8 chars: ${keys.join(', ')}`,\n )\n },\n )\n\n if (leaks.length > 0) {\n const details = leaks.map(l => ` ✗ ${l.key} found in ${l.chunk}`).join('\\n')\n throw new Error(\n `[vite-env] Server environment variables detected in client bundle!\\n\\n${details}\\n\\n These variables are marked as server-only and must never reach the browser.`,\n )\n }\n },\n\n configureServer(server) {\n const envDir = resolvedConfig.envDir || resolvedConfig.root\n server.watcher.add(path.join(envDir, '.env*'))\n\n let debounceTimer: ReturnType<typeof setTimeout>\n\n server.watcher.on('change', async (file) => {\n if (!path.basename(file).startsWith('.env'))\n return\n\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(async () => {\n try {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = await validateAndFormat(envDefinition, rawEnv)\n\n if ('error' in result) {\n resolvedConfig.logger.warn(\n `\\n \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidation failed:\\n${result.error}`,\n )\n return\n }\n\n lastValidated = result.data\n\n const clientMod = server.moduleGraph.getModuleById('\\0virtual:env/client')\n const serverMod = server.moduleGraph.getModuleById('\\0virtual:env/server')\n if (clientMod)\n server.moduleGraph.invalidateModule(clientMod)\n if (serverMod)\n server.moduleGraph.invalidateModule(serverMod)\n if (clientMod || serverMod) {\n serverModuleGuardFails = []\n server.hot.send({ type: 'full-reload' })\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidated`,\n )\n }\n }\n catch (e) {\n resolvedConfig.logger.error(\n `\\n \\x1B[31m✗\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Failed to reload env files: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n }, 150)\n })\n },\n }\n}\n"],"mappings":"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"}
|