@bintvn/lite-env 1.0.0 → 1.0.2
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 +7 -7
- package/package.json +4 -1
- package/src/helper.ts +0 -137
- package/src/index.ts +0 -76
- package/src/types.ts +0 -21
- package/tsconfig.json +0 -26
- package/tsup.config.ts +0 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# lite-env
|
|
1
|
+
# @bintvn/lite-env
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@bintvn/lite-env` is a small TypeScript-first environment loader for Node.js. It reads `.env` and `.env.{NODE_ENV}`, merges them, parses values into runtime types, and returns a type-safe object inferred from your schema.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install lite-env
|
|
16
|
+
npm install @bintvn/lite-env
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
@@ -22,7 +22,7 @@ Create your env files:
|
|
|
22
22
|
|
|
23
23
|
```env
|
|
24
24
|
# .env
|
|
25
|
-
APP_NAME
|
|
25
|
+
APP_NAME=@bintvn/lite-env
|
|
26
26
|
PORT=3000
|
|
27
27
|
DEBUG=false
|
|
28
28
|
```
|
|
@@ -36,7 +36,7 @@ TAGS=api,dev,local
|
|
|
36
36
|
Use `loadEnv` in your app:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
import { loadEnv } from 'lite-env'
|
|
39
|
+
import { loadEnv } from '@bintvn/lite-env'
|
|
40
40
|
|
|
41
41
|
const env = loadEnv({
|
|
42
42
|
APP_NAME: 'string',
|
|
@@ -113,7 +113,7 @@ const env = loadEnv({
|
|
|
113
113
|
|
|
114
114
|
## Supported Types
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
`@bintvn/lite-env` currently supports the following schema kinds:
|
|
117
117
|
|
|
118
118
|
| Schema value | Result type | Notes |
|
|
119
119
|
| --- | --- | --- |
|
|
@@ -127,7 +127,7 @@ const env = loadEnv({
|
|
|
127
127
|
|
|
128
128
|
## Type Inference
|
|
129
129
|
|
|
130
|
-
The main goal of
|
|
130
|
+
The main goal of `@bintvn/lite-env` is type-safe inference from the input schema.
|
|
131
131
|
|
|
132
132
|
```ts
|
|
133
133
|
const env = loadEnv({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bintvn/lite-env",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "TypeScript based environment variable loader.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"util",
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"require": "./dist/index.cjs"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
24
27
|
"scripts": {
|
|
25
28
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
26
29
|
"typecheck": "tsc --noEmit --project tsconfig.json",
|
package/src/helper.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs"
|
|
2
|
-
import { AllowedEnvKeys, EnvKind, EnvKindMap, LoadedEnv } from "./types.js"
|
|
3
|
-
|
|
4
|
-
export function toString(value: string): string {
|
|
5
|
-
return String(value)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function toNumber(value: string): number {
|
|
9
|
-
const parsed = Number(value)
|
|
10
|
-
if (Number.isNaN(parsed))
|
|
11
|
-
throw new Error('Value is not supported for number')
|
|
12
|
-
|
|
13
|
-
return parsed
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function toArray(value: string): string[] {
|
|
17
|
-
if (!value.includes(','))
|
|
18
|
-
throw new Error('Valur is not supported for array')
|
|
19
|
-
|
|
20
|
-
return value.split(',')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function toBoolean(value: string): boolean {
|
|
24
|
-
if (value === 'true' || value === '1')
|
|
25
|
-
return true
|
|
26
|
-
|
|
27
|
-
if (value === 'false' || value === '0')
|
|
28
|
-
return false
|
|
29
|
-
|
|
30
|
-
throw new Error('Value is not supported for boolean')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function toObject<T>(value: string): T {
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(value)
|
|
36
|
-
} catch (error) {
|
|
37
|
-
throw new Error('Value is not supported for object')
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function toBuffer(value: string, encoding?: BufferEncoding): Buffer {
|
|
42
|
-
try {
|
|
43
|
-
return Buffer.from(value, encoding || 'base64')
|
|
44
|
-
} catch (error) {
|
|
45
|
-
throw new Error('Value is not supported for buffer')
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function toDate(value: string): Date {
|
|
50
|
-
const parsed = new Date(value)
|
|
51
|
-
if (Number.isNaN(parsed.getTime()))
|
|
52
|
-
throw new Error('Value is not supported for date')
|
|
53
|
-
|
|
54
|
-
return parsed
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function parseEnvValue<TKind extends EnvKind>(kind: TKind, value: string): EnvKindMap[TKind] {
|
|
58
|
-
switch (kind) {
|
|
59
|
-
case "string":
|
|
60
|
-
return toString(value) as EnvKindMap[TKind]
|
|
61
|
-
case "number":
|
|
62
|
-
return toNumber(value) as EnvKindMap[TKind]
|
|
63
|
-
case "boolean":
|
|
64
|
-
return toBoolean(value) as EnvKindMap[TKind]
|
|
65
|
-
case "array":
|
|
66
|
-
return toArray(value) as EnvKindMap[TKind]
|
|
67
|
-
case "object":
|
|
68
|
-
return toObject(value) as EnvKindMap[TKind]
|
|
69
|
-
case "buffer":
|
|
70
|
-
return toBuffer(value) as EnvKindMap[TKind]
|
|
71
|
-
case "date":
|
|
72
|
-
return toDate(value) as EnvKindMap[TKind]
|
|
73
|
-
default: {
|
|
74
|
-
const exhaustiveCheck: never = kind
|
|
75
|
-
throw new Error(`Unsupported env kind: ${exhaustiveCheck}`)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function safeParse<TSchema extends AllowedEnvKeys>(allowedEnvKeys: TSchema, values: Record<string, string>) {
|
|
81
|
-
const data: Partial<LoadedEnv<TSchema>> = {}
|
|
82
|
-
const error: string[] = []
|
|
83
|
-
|
|
84
|
-
for (const key of Object.keys(allowedEnvKeys) as Array<keyof TSchema>) {
|
|
85
|
-
const value = values[key as string]
|
|
86
|
-
if (value === undefined) continue
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
data[key] = parseEnvValue(allowedEnvKeys[key], value)
|
|
90
|
-
} catch (e) {
|
|
91
|
-
if (e instanceof Error)
|
|
92
|
-
error.push(`${String(key)}: ${e.message}`)
|
|
93
|
-
else
|
|
94
|
-
error.push(`${String(key)}: Unknown error occurred`)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
success: error.length === 0,
|
|
100
|
-
data: data as LoadedEnv<TSchema>,
|
|
101
|
-
error
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function formatError(message: string[]): string {
|
|
106
|
-
return message.join('\n')
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function parseRawEnv(rawEnv: string): Record<string, string> {
|
|
110
|
-
const result: Record<string, string> = {}
|
|
111
|
-
const lines = rawEnv.split(/\r?\n/)
|
|
112
|
-
|
|
113
|
-
for (const line of lines) {
|
|
114
|
-
const normalized = line.trim()
|
|
115
|
-
if (!normalized || normalized.startsWith('#')) continue
|
|
116
|
-
|
|
117
|
-
const separatorIndex = normalized.indexOf('=')
|
|
118
|
-
if (separatorIndex <= 0) continue
|
|
119
|
-
|
|
120
|
-
const key = normalized.slice(0, separatorIndex).trim()
|
|
121
|
-
const value = normalized.slice(separatorIndex + 1).trim()
|
|
122
|
-
if (!key) continue
|
|
123
|
-
|
|
124
|
-
result[key] = value
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return result
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function readEnvFile(filePath: string): false | Record<string, string> {
|
|
131
|
-
if (!existsSync(filePath)) return false
|
|
132
|
-
return parseRawEnv(readFileSync(filePath, 'utf-8'))
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function getUnknownEnvKeys(source: Record<string, string>, allowedEnvKeys: Set<string>): string[] {
|
|
136
|
-
return Object.keys(source).filter((key) => !allowedEnvKeys.has(key))
|
|
137
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { existsSync } from 'fs'
|
|
3
|
-
import { formatError, getUnknownEnvKeys, readEnvFile, safeParse } from './helper.js'
|
|
4
|
-
import { AllowedEnvKeys, LoadedEnv } from "./types.js";
|
|
5
|
-
|
|
6
|
-
export type { AllowedEnvKeys, EnvKind, EnvKindMap, LoadedEnv } from "./types.js"
|
|
7
|
-
|
|
8
|
-
declare global {
|
|
9
|
-
var liteEnv: unknown
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function loadEnv<TSchema extends AllowedEnvKeys = {}>(
|
|
13
|
-
allowedEnvKeys: TSchema = {} as TSchema,
|
|
14
|
-
bypassUnknownEnvKeys: boolean = true,
|
|
15
|
-
NODE_ENV?: string
|
|
16
|
-
): LoadedEnv<TSchema> {
|
|
17
|
-
if (globalThis.liteEnv)
|
|
18
|
-
return globalThis.liteEnv as LoadedEnv<TSchema>
|
|
19
|
-
|
|
20
|
-
if (NODE_ENV) {
|
|
21
|
-
process.env.NODE_ENV = NODE_ENV
|
|
22
|
-
} else {
|
|
23
|
-
if (!process.env.NODE_ENV) {
|
|
24
|
-
process.env.NODE_ENV = 'production'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
NODE_ENV = process.env.NODE_ENV
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const defaultEnvPath = path.join(process.cwd(), '.env')
|
|
31
|
-
const nodeEnvPath = path.join(process.cwd(), `.env.${NODE_ENV}`)
|
|
32
|
-
|
|
33
|
-
if (!existsSync(defaultEnvPath))
|
|
34
|
-
throw new Error('Default Environment file not found')
|
|
35
|
-
|
|
36
|
-
if (!existsSync(nodeEnvPath))
|
|
37
|
-
throw new Error(`Environment file not found: .env.${NODE_ENV}`)
|
|
38
|
-
|
|
39
|
-
const defaultEnvSource = readEnvFile(defaultEnvPath)
|
|
40
|
-
const nodeEnvSource = readEnvFile(nodeEnvPath)
|
|
41
|
-
|
|
42
|
-
const mergedEnv: Record<string, string> = {}
|
|
43
|
-
|
|
44
|
-
if (defaultEnvSource)
|
|
45
|
-
Object.assign(mergedEnv, defaultEnvSource)
|
|
46
|
-
if (nodeEnvSource)
|
|
47
|
-
Object.assign(mergedEnv, nodeEnvSource)
|
|
48
|
-
|
|
49
|
-
if (!bypassUnknownEnvKeys) {
|
|
50
|
-
const unknownKeys = [...new Set([...getUnknownEnvKeys(mergedEnv, new Set(Object.keys(allowedEnvKeys)))])]
|
|
51
|
-
|
|
52
|
-
if (unknownKeys.length > 0)
|
|
53
|
-
throw new Error(`Unknown environment variables in env files: ${unknownKeys.join(', ')}`)
|
|
54
|
-
} else {
|
|
55
|
-
process.env = {
|
|
56
|
-
...process.env,
|
|
57
|
-
...mergedEnv
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const parsedEnv = safeParse(allowedEnvKeys, mergedEnv)
|
|
62
|
-
|
|
63
|
-
if (!parsedEnv.success)
|
|
64
|
-
throw new Error(`Environment validation failed: ${formatError(parsedEnv.error)}`)
|
|
65
|
-
|
|
66
|
-
globalThis.liteEnv = parsedEnv.data
|
|
67
|
-
|
|
68
|
-
process.env = {
|
|
69
|
-
...process.env,
|
|
70
|
-
...parsedEnv.data
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log('Loaded env file', `${defaultEnvPath}, ${nodeEnvPath}`)
|
|
74
|
-
|
|
75
|
-
return parsedEnv.data as LoadedEnv<TSchema>
|
|
76
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type ENV = {
|
|
2
|
-
NODE_ENV: string
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export type EnvKindMap = {
|
|
6
|
-
string: string
|
|
7
|
-
number: number
|
|
8
|
-
boolean: boolean
|
|
9
|
-
array: string[]
|
|
10
|
-
object: unknown
|
|
11
|
-
buffer: Buffer
|
|
12
|
-
date: Date
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type EnvKind = keyof EnvKindMap
|
|
16
|
-
|
|
17
|
-
export type AllowedEnvKeys = Record<string, EnvKind>
|
|
18
|
-
|
|
19
|
-
export type LoadedEnv<TSchema extends AllowedEnvKeys> = {
|
|
20
|
-
[K in keyof TSchema]: EnvKindMap[TSchema[K]]
|
|
21
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"ignoreDeprecations": "6.0",
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "dist",
|
|
6
|
-
"tsBuildInfoFile": "./dist/.tsbuildinfo",
|
|
7
|
-
"types": [
|
|
8
|
-
"node"
|
|
9
|
-
],
|
|
10
|
-
"noImplicitAny": true,
|
|
11
|
-
"module": "NodeNext",
|
|
12
|
-
"moduleResolution": "NodeNext",
|
|
13
|
-
"paths": {
|
|
14
|
-
"@/*": [
|
|
15
|
-
"./src/*"
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"include": [
|
|
20
|
-
"src/**/*"
|
|
21
|
-
],
|
|
22
|
-
"exclude": [
|
|
23
|
-
"node_modules",
|
|
24
|
-
"dist"
|
|
25
|
-
]
|
|
26
|
-
}
|