vite-plugin-typed-env 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/index.cjs +192 -192
- package/dist/index.d.cts +12 -12
- package/dist/index.d.mts +12 -12
- package/dist/index.mjs +192 -192
- package/package.json +9 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vite-plugin-typed-env contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -28,84 +28,102 @@ let node_fs = require("node:fs");
|
|
|
28
28
|
node_fs = __toESM(node_fs);
|
|
29
29
|
let node_path = require("node:path");
|
|
30
30
|
node_path = __toESM(node_path);
|
|
31
|
-
//#region src/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
function parseEnvFile(content) {
|
|
47
|
-
const lines = content.split("\n");
|
|
48
|
-
const entries = [];
|
|
49
|
-
const pendingComments = [];
|
|
50
|
-
for (let i = 0; i < lines.length; i++) {
|
|
51
|
-
const raw = lines[i].trim();
|
|
52
|
-
if (raw === "") {
|
|
53
|
-
pendingComments.length = 0;
|
|
54
|
-
continue;
|
|
31
|
+
//#region src/generator.ts
|
|
32
|
+
function generateDts(items, options) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
lines.push(`// Auto-generated by vite-plugin-typed-env. DO NOT EDIT.`);
|
|
35
|
+
lines.push(`// Re-run vite to regenerate this file.`);
|
|
36
|
+
lines.push(``);
|
|
37
|
+
if (options.augmentImportMeta) {
|
|
38
|
+
lines.push(`/// <reference types="vite/client" />`);
|
|
39
|
+
lines.push(``);
|
|
40
|
+
lines.push(`interface ImportMetaEnv {`);
|
|
41
|
+
for (const { entry, inferred } of items) {
|
|
42
|
+
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
43
|
+
const opt = inferred.isOptional ? "?" : "";
|
|
44
|
+
lines.push(` readonly ${entry.key}${opt}: ${inferred.tsType}`);
|
|
55
45
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
lines.push(`}`);
|
|
47
|
+
lines.push(``);
|
|
48
|
+
lines.push(`interface ImportMeta {`);
|
|
49
|
+
lines.push(` readonly env: ImportMetaEnv`);
|
|
50
|
+
lines.push(`}`);
|
|
51
|
+
} else {
|
|
52
|
+
lines.push(`export interface Env {`);
|
|
53
|
+
for (const { entry, inferred } of items) {
|
|
54
|
+
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
55
|
+
const opt = inferred.isOptional ? "?" : "";
|
|
56
|
+
lines.push(` ${entry.key}${opt}: ${inferred.tsType}`);
|
|
59
57
|
}
|
|
60
|
-
|
|
61
|
-
if (eqIdx === -1) continue;
|
|
62
|
-
const key = raw.slice(0, eqIdx).trim();
|
|
63
|
-
let value = raw.slice(eqIdx + 1).trim();
|
|
64
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
65
|
-
const annotations = parseAnnotations(pendingComments);
|
|
66
|
-
const description = pendingComments.filter((l) => !ANNOTATION_RE.test(l)).map((l) => l.replace(/^#\s*/, "")).join(" ").trim();
|
|
67
|
-
entries.push({
|
|
68
|
-
key,
|
|
69
|
-
value,
|
|
70
|
-
annotations: {
|
|
71
|
-
...annotations,
|
|
72
|
-
description: description || void 0
|
|
73
|
-
},
|
|
74
|
-
comment: ""
|
|
75
|
-
});
|
|
76
|
-
pendingComments.length = 0;
|
|
58
|
+
lines.push(`}`);
|
|
77
59
|
}
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region src/inferrer.ts
|
|
82
|
-
function isBoolean(v) {
|
|
83
|
-
return [
|
|
84
|
-
"true",
|
|
85
|
-
"false",
|
|
86
|
-
"1",
|
|
87
|
-
"0",
|
|
88
|
-
"yes",
|
|
89
|
-
"no"
|
|
90
|
-
].includes(v.toLowerCase());
|
|
91
|
-
}
|
|
92
|
-
function isNumber(v) {
|
|
93
|
-
return v !== "" && !isNaN(Number(v));
|
|
60
|
+
return lines.join("\n");
|
|
94
61
|
}
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
62
|
+
function generateLoader(items, options) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push(`// Auto-generated by vite-plugin-typed-env. DO NOT EDIT.`);
|
|
65
|
+
lines.push(``);
|
|
66
|
+
if (options.schema === "zod") {
|
|
67
|
+
lines.push(`import { envSchema } from './env.schema'`);
|
|
68
|
+
lines.push(``);
|
|
69
|
+
lines.push(`const _parsed = envSchema.safeParse(import.meta.env)`);
|
|
70
|
+
lines.push(``);
|
|
71
|
+
lines.push(`if (!_parsed.success) {`);
|
|
72
|
+
lines.push(` const errors = _parsed.error.flatten().fieldErrors`);
|
|
73
|
+
lines.push(` const msg = Object.entries(errors)`);
|
|
74
|
+
lines.push(` .map(([k, v]) => \` \${k}: \${(v as string[]).join(', ')}\`)`);
|
|
75
|
+
lines.push(` .join('\\n')`);
|
|
76
|
+
lines.push(` throw new Error(\`[env-ts] Invalid environment variables:\\n\${msg}\`)`);
|
|
77
|
+
lines.push(`}`);
|
|
78
|
+
lines.push(``);
|
|
79
|
+
lines.push(`export const env = _parsed.data`);
|
|
80
|
+
lines.push(`export default env`);
|
|
81
|
+
} else {
|
|
82
|
+
const required = items.filter(({ inferred }) => !inferred.isOptional);
|
|
83
|
+
lines.push(`const _raw = import.meta.env`);
|
|
84
|
+
lines.push(``);
|
|
85
|
+
if (required.length > 0) {
|
|
86
|
+
lines.push(`const _required = [${required.map(({ entry }) => `'${entry.key}'`).join(", ")}] as const`);
|
|
87
|
+
lines.push(`for (const key of _required) {`);
|
|
88
|
+
lines.push(` if (!_raw[key]) throw new Error(\`[env-ts] Missing required env var: \${key}\`)`);
|
|
89
|
+
lines.push(`}`);
|
|
90
|
+
lines.push(``);
|
|
91
|
+
}
|
|
92
|
+
lines.push(`export const env = _raw as import('./env').Env`);
|
|
93
|
+
lines.push(`export default env`);
|
|
101
94
|
}
|
|
95
|
+
return lines.join("\n");
|
|
102
96
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
function generateZodSchema(items) {
|
|
98
|
+
const lines = [];
|
|
99
|
+
lines.push(`// Auto-generated by vite-plugin-typed-env. DO NOT EDIT.`);
|
|
100
|
+
lines.push(`import { z } from 'zod'`);
|
|
101
|
+
lines.push(``);
|
|
102
|
+
lines.push(`export const envSchema = z.object({`);
|
|
103
|
+
for (const { entry, inferred } of items) {
|
|
104
|
+
if (entry.annotations.description) lines.push(` // ${entry.annotations.description}`);
|
|
105
|
+
lines.push(` ${entry.key}: ${inferred.zodSchema},`);
|
|
106
|
+
}
|
|
107
|
+
lines.push(`})`);
|
|
108
|
+
lines.push(``);
|
|
109
|
+
lines.push(`export type Env = z.infer<typeof envSchema>`);
|
|
110
|
+
return lines.join("\n");
|
|
106
111
|
}
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/inferrer.ts
|
|
114
|
+
function inferType(entry) {
|
|
115
|
+
const { annotations, value } = entry;
|
|
116
|
+
const isOptional = annotations.optional === true || value === "";
|
|
117
|
+
const base = (annotations.type ? inferFromAnnotation(annotations.type) : null) ?? inferFromValue(value);
|
|
118
|
+
let zodSchema = base.zodSchema;
|
|
119
|
+
if (annotations.default !== void 0) zodSchema = `${zodSchema}.default('${annotations.default}')`;
|
|
120
|
+
else if (isOptional) zodSchema = `${zodSchema}.optional()`;
|
|
121
|
+
return {
|
|
122
|
+
defaultValue: annotations.default,
|
|
123
|
+
isOptional,
|
|
124
|
+
tsType: base.tsType,
|
|
125
|
+
zodSchema
|
|
126
|
+
};
|
|
109
127
|
}
|
|
110
128
|
function inferFromAnnotation(ann) {
|
|
111
129
|
const enumMatch = ann.match(/^enum\((.+)\)$/);
|
|
@@ -176,104 +194,117 @@ function inferFromValue(value) {
|
|
|
176
194
|
zodSchema: "z.string().min(1)"
|
|
177
195
|
};
|
|
178
196
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
function isBoolean(v) {
|
|
198
|
+
return [
|
|
199
|
+
"0",
|
|
200
|
+
"1",
|
|
201
|
+
"false",
|
|
202
|
+
"no",
|
|
203
|
+
"true",
|
|
204
|
+
"yes"
|
|
205
|
+
].includes(v.toLowerCase());
|
|
206
|
+
}
|
|
207
|
+
function isNumber(v) {
|
|
208
|
+
return v !== "" && !isNaN(Number(v));
|
|
209
|
+
}
|
|
210
|
+
function isNumberArray(v) {
|
|
211
|
+
if (!v.includes(",")) return false;
|
|
212
|
+
return v.split(",").every((s) => isNumber(s.trim()));
|
|
213
|
+
}
|
|
214
|
+
function isStringArray(v) {
|
|
215
|
+
return v.includes(",") && v.split(",").length > 1;
|
|
216
|
+
}
|
|
217
|
+
function isUrl(v) {
|
|
218
|
+
try {
|
|
219
|
+
new URL(v);
|
|
220
|
+
return v.startsWith("http://") || v.startsWith("https://") || v.includes("://");
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
192
224
|
}
|
|
193
225
|
//#endregion
|
|
194
|
-
//#region src/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
lines
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
206
|
-
const opt = inferred.isOptional ? "?" : "";
|
|
207
|
-
lines.push(` readonly ${entry.key}${opt}: ${inferred.tsType}`);
|
|
226
|
+
//#region src/parser.ts
|
|
227
|
+
const ANNOTATION_RE = /^#\s*@(\w+)(?::\s*(.+))?$/;
|
|
228
|
+
function parseEnvFile(content) {
|
|
229
|
+
const lines = content.split("\n");
|
|
230
|
+
const entries = [];
|
|
231
|
+
const pendingComments = [];
|
|
232
|
+
for (let i = 0; i < lines.length; i++) {
|
|
233
|
+
const raw = lines[i].trim();
|
|
234
|
+
if (raw === "") {
|
|
235
|
+
pendingComments.length = 0;
|
|
236
|
+
continue;
|
|
208
237
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
lines.push(` readonly env: ImportMetaEnv`);
|
|
213
|
-
lines.push(`}`);
|
|
214
|
-
} else {
|
|
215
|
-
lines.push(`export interface Env {`);
|
|
216
|
-
for (const { entry, inferred } of items) {
|
|
217
|
-
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
218
|
-
const opt = inferred.isOptional ? "?" : "";
|
|
219
|
-
lines.push(` ${entry.key}${opt}: ${inferred.tsType}`);
|
|
238
|
+
if (raw.startsWith("#")) {
|
|
239
|
+
pendingComments.push(raw);
|
|
240
|
+
continue;
|
|
220
241
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
const eqIdx = raw.indexOf("=");
|
|
243
|
+
if (eqIdx === -1) continue;
|
|
244
|
+
const key = raw.slice(0, eqIdx).trim();
|
|
245
|
+
let value = raw.slice(eqIdx + 1).trim();
|
|
246
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
247
|
+
const annotations = parseAnnotations(pendingComments);
|
|
248
|
+
const description = pendingComments.filter((l) => !ANNOTATION_RE.test(l)).map((l) => l.replace(/^#\s*/, "")).join(" ").trim();
|
|
249
|
+
entries.push({
|
|
250
|
+
annotations: {
|
|
251
|
+
...annotations,
|
|
252
|
+
description: description || void 0
|
|
253
|
+
},
|
|
254
|
+
comment: "",
|
|
255
|
+
key,
|
|
256
|
+
value
|
|
257
|
+
});
|
|
258
|
+
pendingComments.length = 0;
|
|
234
259
|
}
|
|
235
|
-
|
|
236
|
-
lines.push(``);
|
|
237
|
-
lines.push(`export type Env = z.infer<typeof envSchema>`);
|
|
238
|
-
return lines.join("\n");
|
|
260
|
+
return entries;
|
|
239
261
|
}
|
|
240
|
-
function
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
lines.push(` const errors = _parsed.error.flatten().fieldErrors`);
|
|
251
|
-
lines.push(` const msg = Object.entries(errors)`);
|
|
252
|
-
lines.push(` .map(([k, v]) => \` \${k}: \${(v as string[]).join(', ')}\`)`);
|
|
253
|
-
lines.push(` .join('\\n')`);
|
|
254
|
-
lines.push(` throw new Error(\`[env-ts] Invalid environment variables:\\n\${msg}\`)`);
|
|
255
|
-
lines.push(`}`);
|
|
256
|
-
lines.push(``);
|
|
257
|
-
lines.push(`export const env = _parsed.data`);
|
|
258
|
-
lines.push(`export default env`);
|
|
259
|
-
} else {
|
|
260
|
-
const required = items.filter(({ inferred }) => !inferred.isOptional);
|
|
261
|
-
lines.push(`const _raw = import.meta.env`);
|
|
262
|
-
lines.push(``);
|
|
263
|
-
if (required.length > 0) {
|
|
264
|
-
lines.push(`const _required = [${required.map(({ entry }) => `'${entry.key}'`).join(", ")}] as const`);
|
|
265
|
-
lines.push(`for (const key of _required) {`);
|
|
266
|
-
lines.push(` if (!_raw[key]) throw new Error(\`[env-ts] Missing required env var: \${key}\`)`);
|
|
267
|
-
lines.push(`}`);
|
|
268
|
-
lines.push(``);
|
|
269
|
-
}
|
|
270
|
-
lines.push(`export const env = _raw as import('./env').Env`);
|
|
271
|
-
lines.push(`export default env`);
|
|
262
|
+
function parseAnnotations(lines) {
|
|
263
|
+
const ann = {};
|
|
264
|
+
for (const line of lines) {
|
|
265
|
+
const m = line.match(ANNOTATION_RE);
|
|
266
|
+
if (!m) continue;
|
|
267
|
+
const [, key, value] = m;
|
|
268
|
+
if (key === "optional") ann.optional = true;
|
|
269
|
+
else if (key === "type" && value) ann.type = value.trim();
|
|
270
|
+
else if (key === "default" && value) ann.default = value.trim();
|
|
271
|
+
else if (key === "desc" && value) ann.description = value.trim();
|
|
272
272
|
}
|
|
273
|
-
return
|
|
273
|
+
return ann;
|
|
274
274
|
}
|
|
275
275
|
//#endregion
|
|
276
276
|
//#region src/index.ts
|
|
277
|
+
function envTs(userOptions = {}) {
|
|
278
|
+
const options = {
|
|
279
|
+
augmentImportMeta: true,
|
|
280
|
+
envFiles: [],
|
|
281
|
+
output: "src",
|
|
282
|
+
schema: "zod",
|
|
283
|
+
strict: true,
|
|
284
|
+
...userOptions
|
|
285
|
+
};
|
|
286
|
+
let config;
|
|
287
|
+
let outputDir;
|
|
288
|
+
return {
|
|
289
|
+
async buildStart() {
|
|
290
|
+
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
291
|
+
},
|
|
292
|
+
configResolved(resolvedConfig) {
|
|
293
|
+
config = resolvedConfig;
|
|
294
|
+
outputDir = node_path.default.resolve(config.root, options.output);
|
|
295
|
+
},
|
|
296
|
+
enforce: "pre",
|
|
297
|
+
async handleHotUpdate({ file, server }) {
|
|
298
|
+
if (!file) return;
|
|
299
|
+
const fileName = node_path.default.basename(file);
|
|
300
|
+
if (!(fileName.startsWith(".env") || options.envFiles.includes(file))) return;
|
|
301
|
+
console.log(`[env-ts] Detected change in ${fileName}, regenerating...`);
|
|
302
|
+
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
303
|
+
server.hot.send({ type: "full-reload" });
|
|
304
|
+
},
|
|
305
|
+
name: "vite-plugin-typed-env"
|
|
306
|
+
};
|
|
307
|
+
}
|
|
277
308
|
async function generateTypes(envDir, outputDir, options) {
|
|
278
309
|
const envFileNames = [
|
|
279
310
|
".env",
|
|
@@ -310,8 +341,8 @@ async function generateTypes(envDir, outputDir, options) {
|
|
|
310
341
|
}
|
|
311
342
|
node_fs.default.mkdirSync(outputDir, { recursive: true });
|
|
312
343
|
const genOptions = {
|
|
313
|
-
|
|
314
|
-
|
|
344
|
+
augmentImportMeta: options.augmentImportMeta,
|
|
345
|
+
schema: options.schema
|
|
315
346
|
};
|
|
316
347
|
const dtsContent = generateDts(items, genOptions);
|
|
317
348
|
writeIfChanged(node_path.default.join(outputDir, "env.d.ts"), dtsContent);
|
|
@@ -329,37 +360,6 @@ function writeIfChanged(filePath, content) {
|
|
|
329
360
|
}
|
|
330
361
|
node_fs.default.writeFileSync(filePath, content, "utf-8");
|
|
331
362
|
}
|
|
332
|
-
function envTs(userOptions = {}) {
|
|
333
|
-
const options = {
|
|
334
|
-
schema: "zod",
|
|
335
|
-
output: "src",
|
|
336
|
-
augmentImportMeta: true,
|
|
337
|
-
strict: true,
|
|
338
|
-
envFiles: [],
|
|
339
|
-
...userOptions
|
|
340
|
-
};
|
|
341
|
-
let config;
|
|
342
|
-
let outputDir;
|
|
343
|
-
return {
|
|
344
|
-
name: "vite-plugin-typed-env",
|
|
345
|
-
enforce: "pre",
|
|
346
|
-
configResolved(resolvedConfig) {
|
|
347
|
-
config = resolvedConfig;
|
|
348
|
-
outputDir = node_path.default.resolve(config.root, options.output);
|
|
349
|
-
},
|
|
350
|
-
async buildStart() {
|
|
351
|
-
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
352
|
-
},
|
|
353
|
-
async handleHotUpdate({ file, server }) {
|
|
354
|
-
if (!file) return;
|
|
355
|
-
const fileName = node_path.default.basename(file);
|
|
356
|
-
if (!(fileName.startsWith(".env") || options.envFiles.includes(file))) return;
|
|
357
|
-
console.log(`[env-ts] Detected change in ${fileName}, regenerating...`);
|
|
358
|
-
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
359
|
-
server.hot.send({ type: "full-reload" });
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
363
|
//#endregion
|
|
364
364
|
exports.default = envTs;
|
|
365
365
|
exports.generateTypes = generateTypes;
|
package/dist/index.d.cts
CHANGED
|
@@ -3,33 +3,33 @@ import { Plugin } from "vite";
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
interface EnvTsOptions {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 是否扩展 Vite 的 ImportMetaEnv 类型
|
|
7
|
+
* 开启后 import.meta.env.YOUR_VAR 自动有类型
|
|
8
|
+
* @default true
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
+
augmentImportMeta?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 额外监听的 .env 文件(默认自动检测 .env, .env.local 等)
|
|
13
|
+
*/
|
|
14
|
+
envFiles?: string[];
|
|
10
15
|
/**
|
|
11
16
|
* 生成文件的输出目录(相对于项目根目录)
|
|
12
17
|
* @default 'src'
|
|
13
18
|
*/
|
|
14
19
|
output?: string;
|
|
15
20
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @default true
|
|
21
|
+
* 生成 Zod schema 文件
|
|
22
|
+
* @default 'zod'
|
|
19
23
|
*/
|
|
20
|
-
|
|
24
|
+
schema?: 'zod' | false;
|
|
21
25
|
/**
|
|
22
26
|
* 缺失必填变量时是否让构建失败
|
|
23
27
|
* @default true
|
|
24
28
|
*/
|
|
25
29
|
strict?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* 额外监听的 .env 文件(默认自动检测 .env, .env.local 等)
|
|
28
|
-
*/
|
|
29
|
-
envFiles?: string[];
|
|
30
30
|
}
|
|
31
|
-
declare function generateTypes(envDir: string, outputDir: string, options: Required<EnvTsOptions>): Promise<void>;
|
|
32
31
|
declare function envTs(userOptions?: EnvTsOptions): Plugin;
|
|
32
|
+
declare function generateTypes(envDir: string, outputDir: string, options: Required<EnvTsOptions>): Promise<void>;
|
|
33
33
|
//#endregion
|
|
34
34
|
export { EnvTsOptions, envTs as default, generateTypes };
|
|
35
35
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -3,33 +3,33 @@ import { Plugin } from "vite";
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
interface EnvTsOptions {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 是否扩展 Vite 的 ImportMetaEnv 类型
|
|
7
|
+
* 开启后 import.meta.env.YOUR_VAR 自动有类型
|
|
8
|
+
* @default true
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
+
augmentImportMeta?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 额外监听的 .env 文件(默认自动检测 .env, .env.local 等)
|
|
13
|
+
*/
|
|
14
|
+
envFiles?: string[];
|
|
10
15
|
/**
|
|
11
16
|
* 生成文件的输出目录(相对于项目根目录)
|
|
12
17
|
* @default 'src'
|
|
13
18
|
*/
|
|
14
19
|
output?: string;
|
|
15
20
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @default true
|
|
21
|
+
* 生成 Zod schema 文件
|
|
22
|
+
* @default 'zod'
|
|
19
23
|
*/
|
|
20
|
-
|
|
24
|
+
schema?: 'zod' | false;
|
|
21
25
|
/**
|
|
22
26
|
* 缺失必填变量时是否让构建失败
|
|
23
27
|
* @default true
|
|
24
28
|
*/
|
|
25
29
|
strict?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* 额外监听的 .env 文件(默认自动检测 .env, .env.local 等)
|
|
28
|
-
*/
|
|
29
|
-
envFiles?: string[];
|
|
30
30
|
}
|
|
31
|
-
declare function generateTypes(envDir: string, outputDir: string, options: Required<EnvTsOptions>): Promise<void>;
|
|
32
31
|
declare function envTs(userOptions?: EnvTsOptions): Plugin;
|
|
32
|
+
declare function generateTypes(envDir: string, outputDir: string, options: Required<EnvTsOptions>): Promise<void>;
|
|
33
33
|
//#endregion
|
|
34
34
|
export { EnvTsOptions, envTs as default, generateTypes };
|
|
35
35
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,83 +1,101 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
//#region src/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
function parseEnvFile(content) {
|
|
19
|
-
const lines = content.split("\n");
|
|
20
|
-
const entries = [];
|
|
21
|
-
const pendingComments = [];
|
|
22
|
-
for (let i = 0; i < lines.length; i++) {
|
|
23
|
-
const raw = lines[i].trim();
|
|
24
|
-
if (raw === "") {
|
|
25
|
-
pendingComments.length = 0;
|
|
26
|
-
continue;
|
|
3
|
+
//#region src/generator.ts
|
|
4
|
+
function generateDts(items, options) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
lines.push(`// Auto-generated by vite-plugin-typed-env. DO NOT EDIT.`);
|
|
7
|
+
lines.push(`// Re-run vite to regenerate this file.`);
|
|
8
|
+
lines.push(``);
|
|
9
|
+
if (options.augmentImportMeta) {
|
|
10
|
+
lines.push(`/// <reference types="vite/client" />`);
|
|
11
|
+
lines.push(``);
|
|
12
|
+
lines.push(`interface ImportMetaEnv {`);
|
|
13
|
+
for (const { entry, inferred } of items) {
|
|
14
|
+
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
15
|
+
const opt = inferred.isOptional ? "?" : "";
|
|
16
|
+
lines.push(` readonly ${entry.key}${opt}: ${inferred.tsType}`);
|
|
27
17
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
lines.push(`}`);
|
|
19
|
+
lines.push(``);
|
|
20
|
+
lines.push(`interface ImportMeta {`);
|
|
21
|
+
lines.push(` readonly env: ImportMetaEnv`);
|
|
22
|
+
lines.push(`}`);
|
|
23
|
+
} else {
|
|
24
|
+
lines.push(`export interface Env {`);
|
|
25
|
+
for (const { entry, inferred } of items) {
|
|
26
|
+
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
27
|
+
const opt = inferred.isOptional ? "?" : "";
|
|
28
|
+
lines.push(` ${entry.key}${opt}: ${inferred.tsType}`);
|
|
31
29
|
}
|
|
32
|
-
|
|
33
|
-
if (eqIdx === -1) continue;
|
|
34
|
-
const key = raw.slice(0, eqIdx).trim();
|
|
35
|
-
let value = raw.slice(eqIdx + 1).trim();
|
|
36
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
37
|
-
const annotations = parseAnnotations(pendingComments);
|
|
38
|
-
const description = pendingComments.filter((l) => !ANNOTATION_RE.test(l)).map((l) => l.replace(/^#\s*/, "")).join(" ").trim();
|
|
39
|
-
entries.push({
|
|
40
|
-
key,
|
|
41
|
-
value,
|
|
42
|
-
annotations: {
|
|
43
|
-
...annotations,
|
|
44
|
-
description: description || void 0
|
|
45
|
-
},
|
|
46
|
-
comment: ""
|
|
47
|
-
});
|
|
48
|
-
pendingComments.length = 0;
|
|
30
|
+
lines.push(`}`);
|
|
49
31
|
}
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/inferrer.ts
|
|
54
|
-
function isBoolean(v) {
|
|
55
|
-
return [
|
|
56
|
-
"true",
|
|
57
|
-
"false",
|
|
58
|
-
"1",
|
|
59
|
-
"0",
|
|
60
|
-
"yes",
|
|
61
|
-
"no"
|
|
62
|
-
].includes(v.toLowerCase());
|
|
63
|
-
}
|
|
64
|
-
function isNumber(v) {
|
|
65
|
-
return v !== "" && !isNaN(Number(v));
|
|
32
|
+
return lines.join("\n");
|
|
66
33
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
34
|
+
function generateLoader(items, options) {
|
|
35
|
+
const lines = [];
|
|
36
|
+
lines.push(`// Auto-generated by vite-plugin-typed-env. DO NOT EDIT.`);
|
|
37
|
+
lines.push(``);
|
|
38
|
+
if (options.schema === "zod") {
|
|
39
|
+
lines.push(`import { envSchema } from './env.schema'`);
|
|
40
|
+
lines.push(``);
|
|
41
|
+
lines.push(`const _parsed = envSchema.safeParse(import.meta.env)`);
|
|
42
|
+
lines.push(``);
|
|
43
|
+
lines.push(`if (!_parsed.success) {`);
|
|
44
|
+
lines.push(` const errors = _parsed.error.flatten().fieldErrors`);
|
|
45
|
+
lines.push(` const msg = Object.entries(errors)`);
|
|
46
|
+
lines.push(` .map(([k, v]) => \` \${k}: \${(v as string[]).join(', ')}\`)`);
|
|
47
|
+
lines.push(` .join('\\n')`);
|
|
48
|
+
lines.push(` throw new Error(\`[env-ts] Invalid environment variables:\\n\${msg}\`)`);
|
|
49
|
+
lines.push(`}`);
|
|
50
|
+
lines.push(``);
|
|
51
|
+
lines.push(`export const env = _parsed.data`);
|
|
52
|
+
lines.push(`export default env`);
|
|
53
|
+
} else {
|
|
54
|
+
const required = items.filter(({ inferred }) => !inferred.isOptional);
|
|
55
|
+
lines.push(`const _raw = import.meta.env`);
|
|
56
|
+
lines.push(``);
|
|
57
|
+
if (required.length > 0) {
|
|
58
|
+
lines.push(`const _required = [${required.map(({ entry }) => `'${entry.key}'`).join(", ")}] as const`);
|
|
59
|
+
lines.push(`for (const key of _required) {`);
|
|
60
|
+
lines.push(` if (!_raw[key]) throw new Error(\`[env-ts] Missing required env var: \${key}\`)`);
|
|
61
|
+
lines.push(`}`);
|
|
62
|
+
lines.push(``);
|
|
63
|
+
}
|
|
64
|
+
lines.push(`export const env = _raw as import('./env').Env`);
|
|
65
|
+
lines.push(`export default env`);
|
|
73
66
|
}
|
|
67
|
+
return lines.join("\n");
|
|
74
68
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
function generateZodSchema(items) {
|
|
70
|
+
const lines = [];
|
|
71
|
+
lines.push(`// Auto-generated by vite-plugin-typed-env. DO NOT EDIT.`);
|
|
72
|
+
lines.push(`import { z } from 'zod'`);
|
|
73
|
+
lines.push(``);
|
|
74
|
+
lines.push(`export const envSchema = z.object({`);
|
|
75
|
+
for (const { entry, inferred } of items) {
|
|
76
|
+
if (entry.annotations.description) lines.push(` // ${entry.annotations.description}`);
|
|
77
|
+
lines.push(` ${entry.key}: ${inferred.zodSchema},`);
|
|
78
|
+
}
|
|
79
|
+
lines.push(`})`);
|
|
80
|
+
lines.push(``);
|
|
81
|
+
lines.push(`export type Env = z.infer<typeof envSchema>`);
|
|
82
|
+
return lines.join("\n");
|
|
78
83
|
}
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/inferrer.ts
|
|
86
|
+
function inferType(entry) {
|
|
87
|
+
const { annotations, value } = entry;
|
|
88
|
+
const isOptional = annotations.optional === true || value === "";
|
|
89
|
+
const base = (annotations.type ? inferFromAnnotation(annotations.type) : null) ?? inferFromValue(value);
|
|
90
|
+
let zodSchema = base.zodSchema;
|
|
91
|
+
if (annotations.default !== void 0) zodSchema = `${zodSchema}.default('${annotations.default}')`;
|
|
92
|
+
else if (isOptional) zodSchema = `${zodSchema}.optional()`;
|
|
93
|
+
return {
|
|
94
|
+
defaultValue: annotations.default,
|
|
95
|
+
isOptional,
|
|
96
|
+
tsType: base.tsType,
|
|
97
|
+
zodSchema
|
|
98
|
+
};
|
|
81
99
|
}
|
|
82
100
|
function inferFromAnnotation(ann) {
|
|
83
101
|
const enumMatch = ann.match(/^enum\((.+)\)$/);
|
|
@@ -148,104 +166,117 @@ function inferFromValue(value) {
|
|
|
148
166
|
zodSchema: "z.string().min(1)"
|
|
149
167
|
};
|
|
150
168
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
function isBoolean(v) {
|
|
170
|
+
return [
|
|
171
|
+
"0",
|
|
172
|
+
"1",
|
|
173
|
+
"false",
|
|
174
|
+
"no",
|
|
175
|
+
"true",
|
|
176
|
+
"yes"
|
|
177
|
+
].includes(v.toLowerCase());
|
|
178
|
+
}
|
|
179
|
+
function isNumber(v) {
|
|
180
|
+
return v !== "" && !isNaN(Number(v));
|
|
181
|
+
}
|
|
182
|
+
function isNumberArray(v) {
|
|
183
|
+
if (!v.includes(",")) return false;
|
|
184
|
+
return v.split(",").every((s) => isNumber(s.trim()));
|
|
185
|
+
}
|
|
186
|
+
function isStringArray(v) {
|
|
187
|
+
return v.includes(",") && v.split(",").length > 1;
|
|
188
|
+
}
|
|
189
|
+
function isUrl(v) {
|
|
190
|
+
try {
|
|
191
|
+
new URL(v);
|
|
192
|
+
return v.startsWith("http://") || v.startsWith("https://") || v.includes("://");
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
164
196
|
}
|
|
165
197
|
//#endregion
|
|
166
|
-
//#region src/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
lines
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
178
|
-
const opt = inferred.isOptional ? "?" : "";
|
|
179
|
-
lines.push(` readonly ${entry.key}${opt}: ${inferred.tsType}`);
|
|
198
|
+
//#region src/parser.ts
|
|
199
|
+
const ANNOTATION_RE = /^#\s*@(\w+)(?::\s*(.+))?$/;
|
|
200
|
+
function parseEnvFile(content) {
|
|
201
|
+
const lines = content.split("\n");
|
|
202
|
+
const entries = [];
|
|
203
|
+
const pendingComments = [];
|
|
204
|
+
for (let i = 0; i < lines.length; i++) {
|
|
205
|
+
const raw = lines[i].trim();
|
|
206
|
+
if (raw === "") {
|
|
207
|
+
pendingComments.length = 0;
|
|
208
|
+
continue;
|
|
180
209
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
lines.push(` readonly env: ImportMetaEnv`);
|
|
185
|
-
lines.push(`}`);
|
|
186
|
-
} else {
|
|
187
|
-
lines.push(`export interface Env {`);
|
|
188
|
-
for (const { entry, inferred } of items) {
|
|
189
|
-
if (entry.annotations.description) lines.push(` /** ${entry.annotations.description} */`);
|
|
190
|
-
const opt = inferred.isOptional ? "?" : "";
|
|
191
|
-
lines.push(` ${entry.key}${opt}: ${inferred.tsType}`);
|
|
210
|
+
if (raw.startsWith("#")) {
|
|
211
|
+
pendingComments.push(raw);
|
|
212
|
+
continue;
|
|
192
213
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
const eqIdx = raw.indexOf("=");
|
|
215
|
+
if (eqIdx === -1) continue;
|
|
216
|
+
const key = raw.slice(0, eqIdx).trim();
|
|
217
|
+
let value = raw.slice(eqIdx + 1).trim();
|
|
218
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
219
|
+
const annotations = parseAnnotations(pendingComments);
|
|
220
|
+
const description = pendingComments.filter((l) => !ANNOTATION_RE.test(l)).map((l) => l.replace(/^#\s*/, "")).join(" ").trim();
|
|
221
|
+
entries.push({
|
|
222
|
+
annotations: {
|
|
223
|
+
...annotations,
|
|
224
|
+
description: description || void 0
|
|
225
|
+
},
|
|
226
|
+
comment: "",
|
|
227
|
+
key,
|
|
228
|
+
value
|
|
229
|
+
});
|
|
230
|
+
pendingComments.length = 0;
|
|
206
231
|
}
|
|
207
|
-
|
|
208
|
-
lines.push(``);
|
|
209
|
-
lines.push(`export type Env = z.infer<typeof envSchema>`);
|
|
210
|
-
return lines.join("\n");
|
|
232
|
+
return entries;
|
|
211
233
|
}
|
|
212
|
-
function
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
lines.push(` const errors = _parsed.error.flatten().fieldErrors`);
|
|
223
|
-
lines.push(` const msg = Object.entries(errors)`);
|
|
224
|
-
lines.push(` .map(([k, v]) => \` \${k}: \${(v as string[]).join(', ')}\`)`);
|
|
225
|
-
lines.push(` .join('\\n')`);
|
|
226
|
-
lines.push(` throw new Error(\`[env-ts] Invalid environment variables:\\n\${msg}\`)`);
|
|
227
|
-
lines.push(`}`);
|
|
228
|
-
lines.push(``);
|
|
229
|
-
lines.push(`export const env = _parsed.data`);
|
|
230
|
-
lines.push(`export default env`);
|
|
231
|
-
} else {
|
|
232
|
-
const required = items.filter(({ inferred }) => !inferred.isOptional);
|
|
233
|
-
lines.push(`const _raw = import.meta.env`);
|
|
234
|
-
lines.push(``);
|
|
235
|
-
if (required.length > 0) {
|
|
236
|
-
lines.push(`const _required = [${required.map(({ entry }) => `'${entry.key}'`).join(", ")}] as const`);
|
|
237
|
-
lines.push(`for (const key of _required) {`);
|
|
238
|
-
lines.push(` if (!_raw[key]) throw new Error(\`[env-ts] Missing required env var: \${key}\`)`);
|
|
239
|
-
lines.push(`}`);
|
|
240
|
-
lines.push(``);
|
|
241
|
-
}
|
|
242
|
-
lines.push(`export const env = _raw as import('./env').Env`);
|
|
243
|
-
lines.push(`export default env`);
|
|
234
|
+
function parseAnnotations(lines) {
|
|
235
|
+
const ann = {};
|
|
236
|
+
for (const line of lines) {
|
|
237
|
+
const m = line.match(ANNOTATION_RE);
|
|
238
|
+
if (!m) continue;
|
|
239
|
+
const [, key, value] = m;
|
|
240
|
+
if (key === "optional") ann.optional = true;
|
|
241
|
+
else if (key === "type" && value) ann.type = value.trim();
|
|
242
|
+
else if (key === "default" && value) ann.default = value.trim();
|
|
243
|
+
else if (key === "desc" && value) ann.description = value.trim();
|
|
244
244
|
}
|
|
245
|
-
return
|
|
245
|
+
return ann;
|
|
246
246
|
}
|
|
247
247
|
//#endregion
|
|
248
248
|
//#region src/index.ts
|
|
249
|
+
function envTs(userOptions = {}) {
|
|
250
|
+
const options = {
|
|
251
|
+
augmentImportMeta: true,
|
|
252
|
+
envFiles: [],
|
|
253
|
+
output: "src",
|
|
254
|
+
schema: "zod",
|
|
255
|
+
strict: true,
|
|
256
|
+
...userOptions
|
|
257
|
+
};
|
|
258
|
+
let config;
|
|
259
|
+
let outputDir;
|
|
260
|
+
return {
|
|
261
|
+
async buildStart() {
|
|
262
|
+
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
263
|
+
},
|
|
264
|
+
configResolved(resolvedConfig) {
|
|
265
|
+
config = resolvedConfig;
|
|
266
|
+
outputDir = path.resolve(config.root, options.output);
|
|
267
|
+
},
|
|
268
|
+
enforce: "pre",
|
|
269
|
+
async handleHotUpdate({ file, server }) {
|
|
270
|
+
if (!file) return;
|
|
271
|
+
const fileName = path.basename(file);
|
|
272
|
+
if (!(fileName.startsWith(".env") || options.envFiles.includes(file))) return;
|
|
273
|
+
console.log(`[env-ts] Detected change in ${fileName}, regenerating...`);
|
|
274
|
+
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
275
|
+
server.hot.send({ type: "full-reload" });
|
|
276
|
+
},
|
|
277
|
+
name: "vite-plugin-typed-env"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
249
280
|
async function generateTypes(envDir, outputDir, options) {
|
|
250
281
|
const envFileNames = [
|
|
251
282
|
".env",
|
|
@@ -282,8 +313,8 @@ async function generateTypes(envDir, outputDir, options) {
|
|
|
282
313
|
}
|
|
283
314
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
284
315
|
const genOptions = {
|
|
285
|
-
|
|
286
|
-
|
|
316
|
+
augmentImportMeta: options.augmentImportMeta,
|
|
317
|
+
schema: options.schema
|
|
287
318
|
};
|
|
288
319
|
const dtsContent = generateDts(items, genOptions);
|
|
289
320
|
writeIfChanged(path.join(outputDir, "env.d.ts"), dtsContent);
|
|
@@ -301,37 +332,6 @@ function writeIfChanged(filePath, content) {
|
|
|
301
332
|
}
|
|
302
333
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
303
334
|
}
|
|
304
|
-
function envTs(userOptions = {}) {
|
|
305
|
-
const options = {
|
|
306
|
-
schema: "zod",
|
|
307
|
-
output: "src",
|
|
308
|
-
augmentImportMeta: true,
|
|
309
|
-
strict: true,
|
|
310
|
-
envFiles: [],
|
|
311
|
-
...userOptions
|
|
312
|
-
};
|
|
313
|
-
let config;
|
|
314
|
-
let outputDir;
|
|
315
|
-
return {
|
|
316
|
-
name: "vite-plugin-typed-env",
|
|
317
|
-
enforce: "pre",
|
|
318
|
-
configResolved(resolvedConfig) {
|
|
319
|
-
config = resolvedConfig;
|
|
320
|
-
outputDir = path.resolve(config.root, options.output);
|
|
321
|
-
},
|
|
322
|
-
async buildStart() {
|
|
323
|
-
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
324
|
-
},
|
|
325
|
-
async handleHotUpdate({ file, server }) {
|
|
326
|
-
if (!file) return;
|
|
327
|
-
const fileName = path.basename(file);
|
|
328
|
-
if (!(fileName.startsWith(".env") || options.envFiles.includes(file))) return;
|
|
329
|
-
console.log(`[env-ts] Detected change in ${fileName}, regenerating...`);
|
|
330
|
-
await generateTypes(config.envDir === false ? config.root : config.envDir, outputDir, options);
|
|
331
|
-
server.hot.send({ type: "full-reload" });
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
335
|
//#endregion
|
|
336
336
|
export { envTs as default, generateTypes };
|
|
337
337
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-typed-env",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -23,16 +23,8 @@
|
|
|
23
23
|
"dist/*.d.cts",
|
|
24
24
|
"dist/*.d.mts"
|
|
25
25
|
],
|
|
26
|
-
"scripts": {
|
|
27
|
-
"build": "tsdown",
|
|
28
|
-
"typecheck": "tsc --noEmit",
|
|
29
|
-
"test": "vitest run",
|
|
30
|
-
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\" \"*.cjs\" \"*.json\" \"*.md\"",
|
|
31
|
-
"format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\" \"*.cjs\" \"*.json\" \"*.md\""
|
|
32
|
-
},
|
|
33
26
|
"devDependencies": {
|
|
34
27
|
"@types/node": "^25.5.2",
|
|
35
|
-
"prettier": "^3.8.2",
|
|
36
28
|
"tsdown": "^0.21.7",
|
|
37
29
|
"typescript": "^6.0.2",
|
|
38
30
|
"vite": "^8.0.8",
|
|
@@ -46,5 +38,12 @@
|
|
|
46
38
|
"zod": {
|
|
47
39
|
"optional": true
|
|
48
40
|
}
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"dev": "tsdown --watch",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest"
|
|
49
48
|
}
|
|
50
|
-
}
|
|
49
|
+
}
|