@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 CHANGED
@@ -1,7 +1,8 @@
1
- import { z } from "zod";
2
-
3
1
  //#region src/env.d.ts
4
- declare function createEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.mts","names":[],"sources":["../src/env.ts"],"mappings":";;;iBAGgB,SAAA,WAAoB,CAAA,CAAE,UAAA,CAAA,CAAY,MAAA,EAAQ,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAA"}
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
- for (const [key, value] of Object.entries(fileEnvironment)) process.env[key] ??= value;
19
- return schema.parse(process.env);
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'\nimport { z } from 'zod'\n\nexport function createEnv<T extends z.ZodTypeAny>(schema: T): z.infer<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\tfor (const [key, value] of Object.entries(fileEnvironment)) {\n\t\tprocess.env[key] ??= value\n\t}\n\n\treturn schema.parse(process.env)\n}\n"],"mappings":";;AAGA,SAAgB,UAAkC,QAAuB;CACxE,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,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,gBAAgB,EACzD,QAAQ,IAAI,SAAS;CAGtB,OAAO,OAAO,MAAM,QAAQ,IAAI"}
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.8",
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
  }
@@ -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
- export function createEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T> {
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
- for (const [key, value] of Object.entries(fileEnvironment)) {
24
- process.env[key] ??= value
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 schema.parse(process.env)
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
  }