@aamini/lib 0.0.8 → 0.0.11
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/dist/env.d.mts +4 -3
- package/dist/env.d.mts.map +1 -1
- package/dist/env.mjs +22 -2
- package/dist/env.mjs.map +1 -1
- package/package.json +3 -1
- package/src/env.test.ts +82 -0
- package/src/env.ts +32 -5
package/dist/env.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
1
|
//#region src/env.d.ts
|
|
4
|
-
|
|
2
|
+
type EnvSchema<T extends object> = {
|
|
3
|
+
parse(input: unknown): T;
|
|
4
|
+
};
|
|
5
|
+
declare function createEnv<T extends object>(schema: EnvSchema<T>): T;
|
|
5
6
|
//#endregion
|
|
6
7
|
export { createEnv };
|
|
7
8
|
//# sourceMappingURL=env.d.mts.map
|
package/dist/env.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.d.mts","names":[],"sources":["../src/env.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"env.d.mts","names":[],"sources":["../src/env.ts"],"mappings":";KAEK,SAAA;EACJ,KAAA,CAAM,KAAA,YAAiB,CAAA;AAAA;AAAA,iBAGR,SAAA,kBAAA,CAA4B,MAAA,EAAQ,SAAA,CAAU,CAAA,IAAK,CAAA"}
|
package/dist/env.mjs
CHANGED
|
@@ -15,8 +15,28 @@ function createEnv(schema) {
|
|
|
15
15
|
override: true,
|
|
16
16
|
processEnv: fileEnvironment
|
|
17
17
|
});
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let parsed;
|
|
19
|
+
function parseEnv() {
|
|
20
|
+
if (parsed !== void 0) return parsed;
|
|
21
|
+
const env = { ...fileEnvironment };
|
|
22
|
+
for (const [key, value] of Object.entries(process.env)) if (value !== void 0) env[key] = value;
|
|
23
|
+
parsed = schema.parse(env);
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
return new Proxy({}, {
|
|
27
|
+
get(_target, property, receiver) {
|
|
28
|
+
return Reflect.get(parseEnv(), property, receiver);
|
|
29
|
+
},
|
|
30
|
+
has(_target, property) {
|
|
31
|
+
return property in parseEnv();
|
|
32
|
+
},
|
|
33
|
+
ownKeys() {
|
|
34
|
+
return Reflect.ownKeys(parseEnv());
|
|
35
|
+
},
|
|
36
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
37
|
+
return Object.getOwnPropertyDescriptor(parseEnv(), property);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
20
40
|
}
|
|
21
41
|
//#endregion
|
|
22
42
|
export { createEnv };
|
package/dist/env.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.mjs","names":[],"sources":["../src/env.ts"],"sourcesContent":["import { config } from 'dotenv'\
|
|
1
|
+
{"version":3,"file":"env.mjs","names":[],"sources":["../src/env.ts"],"sourcesContent":["import { config } from 'dotenv'\n\ntype EnvSchema<T extends object> = {\n\tparse(input: unknown): T\n}\n\nexport function createEnv<T extends object>(schema: EnvSchema<T>): T {\n\tconst raw =\n\t\tprocess.env.RAILWAY_ENVIRONMENT_NAME ??\n\t\t(process.env.NODE_ENV === 'production' ? 'production' : 'development')\n\tconst environmentName = /(?:^|-)pr-\\d+$/.test(raw) ? 'staging' : raw\n\tconst fileEnvironment: NodeJS.ProcessEnv = {}\n\n\tconfig({\n\t\tpath: [\n\t\t\t'.env',\n\t\t\t'.env.local',\n\t\t\t`.env.${environmentName}`,\n\t\t\t`.env.${environmentName}.local`,\n\t\t],\n\t\tquiet: true,\n\t\toverride: true,\n\t\tprocessEnv: fileEnvironment,\n\t})\n\n\tlet parsed: T | undefined\n\n\tfunction parseEnv() {\n\t\tif (parsed !== undefined) return parsed\n\n\t\tconst env: NodeJS.ProcessEnv = { ...fileEnvironment }\n\n\t\tfor (const [key, value] of Object.entries(process.env)) {\n\t\t\tif (value !== undefined) env[key] = value\n\t\t}\n\n\t\tparsed = schema.parse(env)\n\t\treturn parsed\n\t}\n\n\treturn new Proxy({} as Record<PropertyKey, unknown>, {\n\t\tget(_target, property, receiver) {\n\t\t\treturn Reflect.get(parseEnv() as object, property, receiver)\n\t\t},\n\t\thas(_target, property) {\n\t\t\treturn property in (parseEnv() as object)\n\t\t},\n\t\townKeys() {\n\t\t\treturn Reflect.ownKeys(parseEnv() as object)\n\t\t},\n\t\tgetOwnPropertyDescriptor(_target, property) {\n\t\t\treturn Object.getOwnPropertyDescriptor(parseEnv() as object, property)\n\t\t},\n\t}) as T\n}\n"],"mappings":";;AAMA,SAAgB,UAA4B,QAAyB;CACpE,MAAM,MACL,QAAQ,IAAI,6BACX,QAAQ,IAAI,aAAa,eAAe,eAAe;CACzD,MAAM,kBAAkB,iBAAiB,KAAK,IAAI,GAAG,YAAY;CACjE,MAAM,kBAAqC,EAAE;CAE7C,OAAO;EACN,MAAM;GACL;GACA;GACA,QAAQ;GACR,QAAQ,gBAAgB;GACxB;EACD,OAAO;EACP,UAAU;EACV,YAAY;EACZ,CAAC;CAEF,IAAI;CAEJ,SAAS,WAAW;EACnB,IAAI,WAAW,KAAA,GAAW,OAAO;EAEjC,MAAM,MAAyB,EAAE,GAAG,iBAAiB;EAErD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,IAAI,EACrD,IAAI,UAAU,KAAA,GAAW,IAAI,OAAO;EAGrC,SAAS,OAAO,MAAM,IAAI;EAC1B,OAAO;;CAGR,OAAO,IAAI,MAAM,EAAE,EAAkC;EACpD,IAAI,SAAS,UAAU,UAAU;GAChC,OAAO,QAAQ,IAAI,UAAU,EAAY,UAAU,SAAS;;EAE7D,IAAI,SAAS,UAAU;GACtB,OAAO,YAAa,UAAU;;EAE/B,UAAU;GACT,OAAO,QAAQ,QAAQ,UAAU,CAAW;;EAE7C,yBAAyB,SAAS,UAAU;GAC3C,OAAO,OAAO,yBAAyB,UAAU,EAAY,SAAS;;EAEvE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aamini/lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "vp pack",
|
|
33
|
+
"bump": "npm version patch --no-git-tag-version",
|
|
33
34
|
"pack": "vp pack",
|
|
34
35
|
"prepare": "vp pack",
|
|
35
36
|
"prepublishOnly": "vp pack",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@aamini/config": "workspace:*",
|
|
45
|
+
"vitest": "catalog:",
|
|
44
46
|
"typescript": "6.0.2"
|
|
45
47
|
}
|
|
46
48
|
}
|
package/src/env.test.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
import { createEnv } from './env.js'
|
|
7
|
+
|
|
8
|
+
describe.sequential('createEnv', () => {
|
|
9
|
+
const cwd = process.cwd()
|
|
10
|
+
const originalDatabaseUrl = process.env.DATABASE_URL
|
|
11
|
+
const originalRailwayEnvironmentName = process.env.RAILWAY_ENVIRONMENT_NAME
|
|
12
|
+
let tempDir = ''
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tempDir = mkdtempSync(join(tmpdir(), 'aamini-lib-env-'))
|
|
16
|
+
process.chdir(tempDir)
|
|
17
|
+
process.env.RAILWAY_ENVIRONMENT_NAME = 'production'
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
process.chdir(cwd)
|
|
22
|
+
if (originalDatabaseUrl === undefined) delete process.env.DATABASE_URL
|
|
23
|
+
else process.env.DATABASE_URL = originalDatabaseUrl
|
|
24
|
+
if (originalRailwayEnvironmentName === undefined)
|
|
25
|
+
delete process.env.RAILWAY_ENVIRONMENT_NAME
|
|
26
|
+
else process.env.RAILWAY_ENVIRONMENT_NAME = originalRailwayEnvironmentName
|
|
27
|
+
if (tempDir) rmSync(tempDir, { recursive: true, force: true })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('keeps injected env values ahead of dotenv files', () => {
|
|
31
|
+
writeFileSync(join(tempDir, '.env'), 'DATABASE_URL=postgres://from-file\n')
|
|
32
|
+
process.env.DATABASE_URL = 'postgres://from-railway'
|
|
33
|
+
|
|
34
|
+
const env = createEnv(
|
|
35
|
+
z.object({
|
|
36
|
+
DATABASE_URL: z.string(),
|
|
37
|
+
}),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(env.DATABASE_URL).toBe('postgres://from-railway')
|
|
41
|
+
expect(process.env.DATABASE_URL).toBe('postgres://from-railway')
|
|
42
|
+
expect(readFileSync(join(tempDir, '.env'), 'utf8')).toContain('from-file')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('loads dotenv files from lowest to highest precedence', () => {
|
|
46
|
+
delete process.env.DATABASE_URL
|
|
47
|
+
writeFileSync(join(tempDir, '.env'), 'DATABASE_URL=postgres://from-env\n')
|
|
48
|
+
writeFileSync(
|
|
49
|
+
join(tempDir, '.env.local'),
|
|
50
|
+
'DATABASE_URL=postgres://from-local\n',
|
|
51
|
+
)
|
|
52
|
+
writeFileSync(
|
|
53
|
+
join(tempDir, '.env.production'),
|
|
54
|
+
'DATABASE_URL=postgres://from-production\n',
|
|
55
|
+
)
|
|
56
|
+
writeFileSync(
|
|
57
|
+
join(tempDir, '.env.production.local'),
|
|
58
|
+
'DATABASE_URL=postgres://from-production-local\n',
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const env = createEnv(
|
|
62
|
+
z.object({
|
|
63
|
+
DATABASE_URL: z.string(),
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
expect(env.DATABASE_URL).toBe('postgres://from-production-local')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('does not validate until an env value is read', () => {
|
|
71
|
+
delete process.env.DATABASE_URL
|
|
72
|
+
|
|
73
|
+
const env = createEnv(
|
|
74
|
+
z.object({
|
|
75
|
+
DATABASE_URL: z.string(),
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
expect(() => env).not.toThrow()
|
|
80
|
+
expect(() => env.DATABASE_URL).toThrow(/expected string/)
|
|
81
|
+
})
|
|
82
|
+
})
|
package/src/env.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { config } from 'dotenv'
|
|
2
|
-
import { z } from 'zod'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
type EnvSchema<T extends object> = {
|
|
4
|
+
parse(input: unknown): T
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createEnv<T extends object>(schema: EnvSchema<T>): T {
|
|
5
8
|
const raw =
|
|
6
9
|
process.env.RAILWAY_ENVIRONMENT_NAME ??
|
|
7
10
|
(process.env.NODE_ENV === 'production' ? 'production' : 'development')
|
|
@@ -20,9 +23,33 @@ export function createEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T> {
|
|
|
20
23
|
processEnv: fileEnvironment,
|
|
21
24
|
})
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
let parsed: T | undefined
|
|
27
|
+
|
|
28
|
+
function parseEnv() {
|
|
29
|
+
if (parsed !== undefined) return parsed
|
|
30
|
+
|
|
31
|
+
const env: NodeJS.ProcessEnv = { ...fileEnvironment }
|
|
32
|
+
|
|
33
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
34
|
+
if (value !== undefined) env[key] = value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
parsed = schema.parse(env)
|
|
38
|
+
return parsed
|
|
25
39
|
}
|
|
26
40
|
|
|
27
|
-
return
|
|
41
|
+
return new Proxy({} as Record<PropertyKey, unknown>, {
|
|
42
|
+
get(_target, property, receiver) {
|
|
43
|
+
return Reflect.get(parseEnv() as object, property, receiver)
|
|
44
|
+
},
|
|
45
|
+
has(_target, property) {
|
|
46
|
+
return property in (parseEnv() as object)
|
|
47
|
+
},
|
|
48
|
+
ownKeys() {
|
|
49
|
+
return Reflect.ownKeys(parseEnv() as object)
|
|
50
|
+
},
|
|
51
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
52
|
+
return Object.getOwnPropertyDescriptor(parseEnv() as object, property)
|
|
53
|
+
},
|
|
54
|
+
}) as T
|
|
28
55
|
}
|