@adonisjs/env 4.2.0-0 → 4.2.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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The MIT License
2
2
 
3
- Copyright (c) 2023 AdonisJS Framework
3
+ Copyright (c) 2023 Harminder Virk
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
package/README.md CHANGED
@@ -46,12 +46,7 @@ Following is the list of loaded files. The array is ordered by the priority of t
46
46
  The `EnvParser` class is responsible for parsing the contents of the `.env` file(s) and converting them into an object.
47
47
 
48
48
  ```ts
49
- import { EnvLoader, EnvParser } from '@adonisjs/env'
50
-
51
- const lookupPath = new URL('./', import.meta.url)
52
- const loader = new EnvLoader(lookupPath)
53
- const envFiles = await loader.load()
54
-
49
+ import { EnvParser } from '@adonisjs/env'
55
50
  const envParser = new EnvParser(`
56
51
  PORT=3000
57
52
  HOST=localhost
@@ -92,6 +87,8 @@ The `Env.rules` method returns an instance of the validator to validate the envi
92
87
  validator.validate(process.env)
93
88
  ```
94
89
 
90
+ ## Complete example
91
+
95
92
  Following is a complete example of loading dot-env files and validating them in one go.
96
93
 
97
94
  > **Note**: Existing `process.env` variables have the top most priority over the variables defined in any of the files.
package/build/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export { EnvLoader } from './src/loader.js';
4
4
  export { EnvEditor } from './src/editor.js';
5
5
  export * as errors from './src/exceptions.js';
6
6
  export { EnvProcessor } from './src/processor.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
@@ -1,3 +1,4 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  declare const _default: import("util").DebugLogger;
3
3
  export default _default;
4
+ //# sourceMappingURL=debug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":";;AAUA,wBAAuC"}
@@ -11,3 +11,4 @@ export declare class EnvEditor {
11
11
  }[];
12
12
  save(): Promise<void>;
13
13
  }
14
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/editor.ts"],"names":[],"mappings":";AAeA,qBAAa,SAAS;;WASP,MAAM,CAAC,OAAO,EAAE,GAAG;gBAOpB,OAAO,EAAE,GAAG;IASlB,IAAI;IAoBV,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO;IASjD,MAAM;;;;IAOA,IAAI;CAOX"}
@@ -25,3 +25,4 @@ export declare class Env<EnvValues extends Record<string, any>> {
25
25
  set<K extends keyof EnvValues>(key: K, value: EnvValues[K]): void;
26
26
  set(key: string, value: string): void;
27
27
  }
28
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":";AASA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAiB7C,qBAAa,GAAG,CAAC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;gBAMxC,MAAM,EAAE,SAAS;WAShB,MAAM,CAAC,MAAM,SAAS;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;KAAE,EACvE,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CACR,GAAG,CAAC;SACD,CAAC,IAAI,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAC3C,CAAC,CACH;IASD,MAAM,CAAC,MAAM;;;;;MAAY;IAOzB,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;KAAE,EAAE,MAAM,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;IAoB1F,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IACpD,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAC3B,GAAG,EAAE,CAAC,EACN,YAAY,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IACpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAoC9C,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;IACjE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAKtC"}
package/build/src/env.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { schema as envSchema } from '@poppinss/validator-lite';
2
2
  import { EnvValidator } from './validator.js';
3
3
  import { EnvProcessor } from './processor.js';
4
- export class Env {
4
+ class Env {
5
5
  #values;
6
6
  constructor(values) {
7
7
  this.#values = values;
@@ -31,3 +31,4 @@ export class Env {
31
31
  process.env[key] = value;
32
32
  }
33
33
  }
34
+ export { Env };
@@ -22,3 +22,4 @@ export declare const E_INVALID_ENV_VARIABLES: {
22
22
  prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
23
23
  stackTraceLimit: number;
24
24
  };
25
+ //# sourceMappingURL=exceptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exceptions.d.ts","sourceRoot":"","sources":["../../src/exceptions.ts"],"names":[],"mappings":";AAeA,eAAO,MAAM,uBAAuB;;;;;cAG5B,MAAM;;;;;;;;;;;;;;;;;CACb,CAAA"}
@@ -8,3 +8,4 @@ export declare class EnvLoader {
8
8
  fileExists: boolean;
9
9
  }[]>;
10
10
  }
11
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/loader.ts"],"names":[],"mappings":";AAqCA,qBAAa,SAAS;;gBAIR,OAAO,EAAE,MAAM,GAAG,GAAG,EAAE,eAAe,GAAE,OAAe;IA0B7D,IAAI,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAiFjF"}
@@ -6,3 +6,4 @@ export declare class EnvParser {
6
6
  });
7
7
  parse(): DotenvParseOutput;
8
8
  }
9
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AASA,OAAe,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAA;AA0ClD,qBAAa,SAAS;;gBAIR,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,EAAE,OAAO,CAAA;KAAE;IAyHxE,KAAK,IAAI,iBAAiB;CAQ3B"}
@@ -4,3 +4,4 @@ export declare class EnvProcessor {
4
4
  constructor(appRoot: URL);
5
5
  process(): Promise<Record<string, any>>;
6
6
  }
7
+ //# sourceMappingURL=processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/processor.ts"],"names":[],"mappings":";AAgBA,qBAAa,YAAY;;gBAMX,OAAO,EAAE,GAAG;IAyDlB,OAAO;CAGd"}
@@ -10,3 +10,4 @@ export declare class EnvValidator<Schema extends {
10
10
  [K in keyof Schema]: ReturnType<Schema[K]>;
11
11
  };
12
12
  }
13
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAUrD,qBAAa,YAAY,CAAC,MAAM,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;CAAE;;gBAIjE,MAAM,EAAE,MAAM;IAY1B,QAAQ,CAAC,MAAM,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GAAG;SACpD,CAAC,IAAI,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAC3C;CAwBF"}
package/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ export { Env } from './src/env.js'
11
+ export { EnvParser } from './src/parser.js'
12
+ export { EnvLoader } from './src/loader.js'
13
+ export { EnvEditor } from './src/editor.js'
14
+ export * as errors from './src/exceptions.js'
15
+ export { EnvProcessor } from './src/processor.js'
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@adonisjs/env",
3
- "version": "4.2.0-0",
3
+ "version": "4.2.0-2",
4
4
  "description": "Environment variable manager for Node.js",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
7
7
  "files": [
8
+ "src",
9
+ "index.ts",
8
10
  "build/src",
9
11
  "build/index.d.ts",
12
+ "build/index.d.ts.map",
10
13
  "build/index.js"
11
14
  ],
12
15
  "exports": {
@@ -33,36 +36,33 @@
33
36
  "author": "virk,adonisjs",
34
37
  "license": "MIT",
35
38
  "devDependencies": {
36
- "@commitlint/cli": "^17.4.2",
37
- "@commitlint/config-conventional": "^17.4.2",
39
+ "@commitlint/cli": "^17.5.0",
40
+ "@commitlint/config-conventional": "^17.4.4",
38
41
  "@japa/assert": "^1.4.1",
39
42
  "@japa/expect-type": "^1.0.3",
40
43
  "@japa/file-system": "^1.0.1",
41
44
  "@japa/run-failed-tests": "^1.1.1",
42
- "@japa/runner": "^2.3.0",
45
+ "@japa/runner": "^2.5.1",
43
46
  "@japa/spec-reporter": "^1.3.3",
44
- "@poppinss/dev-utils": "^2.0.3",
45
- "@swc/core": "^1.3.35",
46
- "@types/fs-extra": "^11.0.1",
47
- "@types/node": "^18.13.0",
48
- "c8": "^7.12.0",
47
+ "@swc/core": "^1.3.42",
48
+ "@types/node": "^18.15.9",
49
+ "c8": "^7.13.0",
49
50
  "cross-env": "^7.0.3",
50
51
  "del-cli": "^5.0.0",
51
- "eslint": "^8.34.0",
52
- "eslint-config-prettier": "^8.6.0",
52
+ "eslint": "^8.36.0",
53
+ "eslint-config-prettier": "^8.8.0",
53
54
  "eslint-plugin-adonis": "^3.0.3",
54
55
  "eslint-plugin-prettier": "^4.2.1",
55
- "fs-extra": "^11.1.0",
56
- "github-label-sync": "^2.2.0",
56
+ "github-label-sync": "^2.3.1",
57
57
  "husky": "^8.0.3",
58
- "np": "^7.6.3",
59
- "prettier": "^2.8.4",
58
+ "np": "^7.6.4",
59
+ "prettier": "^2.8.7",
60
60
  "ts-node": "^10.9.1",
61
- "typescript": "^4.9.5"
61
+ "typescript": "^5.0.2"
62
62
  },
63
63
  "dependencies": {
64
- "@poppinss/utils": "^6.5.0-0",
65
- "@poppinss/validator-lite": "^1.0.2",
64
+ "@poppinss/utils": "^6.5.0-2",
65
+ "@poppinss/validator-lite": "^1.0.3",
66
66
  "dotenv": "^16.0.3",
67
67
  "split-lines": "^3.0.0"
68
68
  },
package/src/debug.ts ADDED
@@ -0,0 +1,11 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import { debuglog } from 'node:util'
11
+ export default debuglog('adonisjs:env')
package/src/editor.ts ADDED
@@ -0,0 +1,84 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import splitLines from 'split-lines'
11
+ import lodash from '@poppinss/utils/lodash'
12
+ import { writeFile } from 'node:fs/promises'
13
+
14
+ import { EnvLoader } from './loader.js'
15
+
16
+ export class EnvEditor {
17
+ #appRoot: URL
18
+ #loader: EnvLoader
19
+ #files: { contents: string[]; path: string }[] = []
20
+
21
+ /**
22
+ * Creates an instance of env editor and loads .env files
23
+ * contents.
24
+ */
25
+ static async create(appRoot: URL) {
26
+ const editor = new EnvEditor(appRoot)
27
+ await editor.load()
28
+
29
+ return editor
30
+ }
31
+
32
+ constructor(appRoot: URL) {
33
+ this.#appRoot = appRoot
34
+ this.#loader = new EnvLoader(this.#appRoot, true)
35
+ }
36
+
37
+ /**
38
+ * Loads .env files for editing. Only ".env" and ".env.example"
39
+ * files are picked for editing.
40
+ */
41
+ async load() {
42
+ const envFiles = await this.#loader.load()
43
+
44
+ this.#files = envFiles
45
+ .filter(
46
+ (envFile) =>
47
+ envFile.fileExists &&
48
+ (envFile.path.endsWith('.env') || envFile.path.endsWith('.env.example'))
49
+ )
50
+ .map((envFile) => {
51
+ return {
52
+ contents: splitLines(envFile.contents.trim()),
53
+ path: envFile.path,
54
+ }
55
+ })
56
+ }
57
+
58
+ /**
59
+ * Add key-value pair to the dot-env files.
60
+ */
61
+ add(key: string, value: string | number | boolean) {
62
+ this.#files.forEach((file) => {
63
+ let entryIndex = file.contents.findIndex((line) => line.startsWith(`${key}=`))
64
+
65
+ entryIndex = entryIndex === -1 ? file.contents.length : entryIndex
66
+ lodash.set(file.contents, entryIndex, `${key}=${String(value)}`)
67
+ })
68
+ }
69
+
70
+ toJSON() {
71
+ return this.#files
72
+ }
73
+
74
+ /**
75
+ * Save changes to the disk
76
+ */
77
+ async save() {
78
+ await Promise.all(
79
+ this.#files.map((file) => {
80
+ return writeFile(file.path, file.contents.join('\n'))
81
+ })
82
+ )
83
+ }
84
+ }
package/src/env.ts ADDED
@@ -0,0 +1,134 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import { schema as envSchema, type ValidateFn } from '@poppinss/validator-lite'
11
+ import { EnvValidator } from './validator.js'
12
+ import { EnvProcessor } from './processor.js'
13
+
14
+ /**
15
+ * A wrapper over "process.env" with types information.
16
+ *
17
+ * ```ts
18
+ * const validate = Env.rules({
19
+ * PORT: Env.schema.number()
20
+ * })
21
+ *
22
+ * const validatedEnvVars = validate(process.env)
23
+ *
24
+ * const env = new EnvValues(validatedEnvVars)
25
+ * env.get('PORT') // type === number
26
+ * ```
27
+ */
28
+ export class Env<EnvValues extends Record<string, any>> {
29
+ /**
30
+ * A cache of env values
31
+ */
32
+ #values: EnvValues
33
+
34
+ constructor(values: EnvValues) {
35
+ this.#values = values
36
+ }
37
+
38
+ /**
39
+ * Create an instance of the env class by validating the
40
+ * environment variables. Also, the `.env` files are
41
+ * loaded from the appRoot
42
+ */
43
+ static async create<Schema extends { [key: string]: ValidateFn<unknown> }>(
44
+ appRoot: URL,
45
+ schema: Schema
46
+ ): Promise<
47
+ Env<{
48
+ [K in keyof Schema]: ReturnType<Schema[K]>
49
+ }>
50
+ > {
51
+ const values = await new EnvProcessor(appRoot).process()
52
+ const validator = this.rules(schema)
53
+ return new Env(validator.validate(values))
54
+ }
55
+
56
+ /**
57
+ * The schema builder for defining validation rules
58
+ */
59
+ static schema = envSchema
60
+
61
+ /**
62
+ * Define the validation rules for validating environment
63
+ * variables. The return value is an instance of the
64
+ * env validator
65
+ */
66
+ static rules<T extends { [key: string]: ValidateFn<unknown> }>(schema: T): EnvValidator<T> {
67
+ const validator = new EnvValidator<T>(schema)
68
+ return validator
69
+ }
70
+
71
+ /**
72
+ * Get the value of an environment variable by key. The values are
73
+ * lookedup inside the validated environment and "process.env"
74
+ * is used as a fallback.
75
+ *
76
+ * The second param is the default value, which is returned when
77
+ * the environment variable does not exist.
78
+ *
79
+ * ```ts
80
+ * Env.get('PORT')
81
+ *
82
+ * // With default value
83
+ * Env.get('PORT', 3000)
84
+ * ```
85
+ */
86
+ get<K extends keyof EnvValues>(key: K): EnvValues[K]
87
+ get<K extends keyof EnvValues>(
88
+ key: K,
89
+ defaultValue: Exclude<EnvValues[K], undefined>
90
+ ): Exclude<EnvValues[K], undefined>
91
+ get(key: string): string | undefined
92
+ get(key: string, defaultValue: string): string
93
+ get(key: string, defaultValue?: any): any {
94
+ /**
95
+ * Return cached value
96
+ */
97
+ if (this.#values[key] !== undefined) {
98
+ return this.#values[key]
99
+ }
100
+
101
+ /**
102
+ * Get value from "process.env" and update the cache
103
+ */
104
+ const envValue = process.env[key]
105
+ if (envValue) {
106
+ return envValue
107
+ }
108
+
109
+ /**
110
+ * Return default value when unable to lookup any other value
111
+ */
112
+ return defaultValue
113
+ }
114
+
115
+ /**
116
+ * Update/set value of an environment variable.
117
+ *
118
+ * The value is not casted/validated using the validator, so make sure
119
+ * to set the correct data type.
120
+ *
121
+ * ```ts
122
+ * Env.set('PORT', 3000)
123
+ *
124
+ * Env.get('PORT') === 3000 // true
125
+ * process.env.PORT === '3000' // true
126
+ * ```
127
+ */
128
+ set<K extends keyof EnvValues>(key: K, value: EnvValues[K]): void
129
+ set(key: string, value: string): void
130
+ set(key: string | keyof EnvValues, value: any): void {
131
+ this.#values[key] = value
132
+ process.env[key as string] = value
133
+ }
134
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import { Exception } from '@poppinss/utils'
11
+
12
+ /**
13
+ * Exception raised when one or more env variables
14
+ * are invalid
15
+ */
16
+ export const E_INVALID_ENV_VARIABLES = class EnvValidationException extends Exception {
17
+ static message = 'Validation failed for one or more environment variables'
18
+ static code = 'E_INVALID_ENV_VARIABLES'
19
+ help: string = ''
20
+ }
package/src/loader.ts ADDED
@@ -0,0 +1,149 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import { fileURLToPath } from 'node:url'
11
+ import { readFile } from 'node:fs/promises'
12
+ import { isAbsolute, join } from 'node:path'
13
+
14
+ import debug from './debug.js'
15
+
16
+ /**
17
+ * Read the contents of one or more dot-env files. Following is how the files
18
+ * are read.
19
+ *
20
+ * - Load file from the "ENV_PATH" environment file.
21
+ * (Raise error if file is missing)
22
+ *
23
+ * - If "ENV_PATH" is not defined, then find ".env" file in the app root.
24
+ * (Ignore if file is missing)
25
+ *
26
+ * - Find ".env.[NODE_ENV]" file in the app root.
27
+ * (Ignore if file is missing)
28
+ *
29
+ * ```ts
30
+ * const loader = new EnvLoader(new URL('./', import.meta.url))
31
+ *
32
+ * const { envContents, currentEnvContents } = await loader.load()
33
+ *
34
+ * // envContents: Contents of .env or file specified via ENV_PATH
35
+ * // currentEnvContents: Contents of .env.[NODE_ENV] file
36
+ * ```
37
+ */
38
+ export class EnvLoader {
39
+ #appRoot: string
40
+ #loadExampleFile: boolean
41
+
42
+ constructor(appRoot: string | URL, loadExampleFile: boolean = false) {
43
+ this.#appRoot = typeof appRoot === 'string' ? appRoot : fileURLToPath(appRoot)
44
+ this.#loadExampleFile = loadExampleFile
45
+ }
46
+
47
+ /**
48
+ * Optionally read a file from the disk
49
+ */
50
+ async #loadFile(filePath: string | URL): Promise<{ fileExists: boolean; contents: string }> {
51
+ try {
52
+ const contents = await readFile(filePath, 'utf-8')
53
+ return { contents, fileExists: true }
54
+ } catch (error) {
55
+ /* c8 ignore next 3 */
56
+ if (error.code !== 'ENOENT') {
57
+ throw error
58
+ }
59
+
60
+ return { contents: '', fileExists: false }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Load contents of the main dot-env file and the current
66
+ * environment dot-env file
67
+ */
68
+ async load(): Promise<{ contents: string; path: string; fileExists: boolean }[]> {
69
+ const ENV_PATH = process.env.ENV_PATH
70
+ const NODE_ENV = process.env.NODE_ENV
71
+ const envFiles: { path: string; contents: string; fileExists: boolean }[] = []
72
+
73
+ if (debug.enabled) {
74
+ debug('ENV_PATH variable is %s', ENV_PATH ? 'set' : 'not set')
75
+ debug('NODE_ENV variable is %s', NODE_ENV ? 'set' : 'not set')
76
+ }
77
+
78
+ /**
79
+ * Base path to load .env files from
80
+ */
81
+ const baseEnvPath = ENV_PATH
82
+ ? isAbsolute(ENV_PATH)
83
+ ? ENV_PATH
84
+ : join(this.#appRoot, ENV_PATH)
85
+ : this.#appRoot
86
+
87
+ if (debug.enabled) {
88
+ debug('dot-env files base path "%s"', baseEnvPath)
89
+ }
90
+
91
+ /**
92
+ * 1st
93
+ * The top most priority is given to the ".env.[NODE_ENV].local" file
94
+ */
95
+ if (NODE_ENV) {
96
+ const nodeEnvLocalFile = join(baseEnvPath, `.env.${NODE_ENV}.local`)
97
+ envFiles.push({
98
+ path: nodeEnvLocalFile,
99
+ ...(await this.#loadFile(nodeEnvLocalFile)),
100
+ })
101
+ }
102
+
103
+ /**
104
+ * 2nd
105
+ * Next, we give priority to the ".env.local" file
106
+ */
107
+ if (!NODE_ENV || !['test', 'testing'].includes(NODE_ENV)) {
108
+ const envLocalFile = join(baseEnvPath, '.env.local')
109
+ envFiles.push({
110
+ path: envLocalFile,
111
+ ...(await this.#loadFile(envLocalFile)),
112
+ })
113
+ }
114
+
115
+ /**
116
+ * 3rd
117
+ * Next, we give priority to the ".env.[NODE_ENV]" file
118
+ */
119
+ if (NODE_ENV) {
120
+ const nodeEnvFile = join(baseEnvPath, `.env.${NODE_ENV}`)
121
+ envFiles.push({
122
+ path: nodeEnvFile,
123
+ ...(await this.#loadFile(nodeEnvFile)),
124
+ })
125
+ }
126
+
127
+ /**
128
+ * Finally, we push the contents of the ".env" file.
129
+ */
130
+ const envFile = join(baseEnvPath, '.env')
131
+ envFiles.push({
132
+ path: envFile,
133
+ ...(await this.#loadFile(envFile)),
134
+ })
135
+
136
+ /**
137
+ * Load example file
138
+ */
139
+ if (this.#loadExampleFile) {
140
+ const envExampleFile = join(baseEnvPath, '.env.example')
141
+ envFiles.push({
142
+ path: envExampleFile,
143
+ ...(await this.#loadFile(envExampleFile)),
144
+ })
145
+ }
146
+
147
+ return envFiles
148
+ }
149
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,185 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import dotenv, { DotenvParseOutput } from 'dotenv'
11
+
12
+ /**
13
+ * Env parser parses the environment variables from a string formatted
14
+ * as a key-value pair seperated using an `=`. For example:
15
+ *
16
+ * ```dotenv
17
+ * PORT=3333
18
+ * HOST=127.0.0.1
19
+ * ```
20
+ *
21
+ * The variables can reference other environment variables as well using `$`.
22
+ * For example:
23
+ *
24
+ * ```dotenv
25
+ * PORT=3333
26
+ * REDIS_PORT=$PORT
27
+ * ```
28
+ *
29
+ * The variables using characters other than letters can wrap variable
30
+ * named inside a curly brace.
31
+ *
32
+ * ```dotenv
33
+ * APP-PORT=3333
34
+ * REDIS_PORT=${APP-PORT}
35
+ * ```
36
+ *
37
+ * You can escape the `$` sign with a backtick.
38
+ *
39
+ * ```dotenv
40
+ * REDIS_PASSWORD=foo\$123
41
+ * ```
42
+ *
43
+ * ## Usage
44
+ *
45
+ * ```ts
46
+ * const parser = new EnvParser(envContents)
47
+ * const output = parser.parse()
48
+ *
49
+ * // The output is a key-value pair
50
+ * ```
51
+ */
52
+ export class EnvParser {
53
+ #envContents: string
54
+ #preferProcessEnv: boolean = true
55
+
56
+ constructor(envContents: string, options?: { ignoreProcessEnv: boolean }) {
57
+ if (options?.ignoreProcessEnv) {
58
+ this.#preferProcessEnv = false
59
+ }
60
+
61
+ this.#envContents = envContents
62
+ }
63
+
64
+ /**
65
+ * Returns the value from the parsed object
66
+ */
67
+ #getValue(key: string, parsed: DotenvParseOutput): string {
68
+ if (this.#preferProcessEnv && process.env[key]) {
69
+ return process.env[key]!
70
+ }
71
+
72
+ if (parsed[key]) {
73
+ return this.#interpolate(parsed[key], parsed)
74
+ }
75
+
76
+ return process.env[key] || ''
77
+ }
78
+
79
+ /**
80
+ * Interpolating the token wrapped inside the mustache braces.
81
+ */
82
+ #interpolateMustache(token: string, parsed: DotenvParseOutput) {
83
+ /**
84
+ * Finding the closing brace. If closing brace is missing, we
85
+ * consider the block as a normal string
86
+ */
87
+ const closingBrace = token.indexOf('}')
88
+ if (closingBrace === -1) {
89
+ return token
90
+ }
91
+
92
+ /**
93
+ * Then we pull everything until the closing brace, except
94
+ * the opening brace and trim off all white spaces.
95
+ */
96
+ const varReference = token.slice(1, closingBrace).trim()
97
+
98
+ /**
99
+ * Getting the value of the reference inside the braces
100
+ */
101
+ return `${this.#getValue(varReference, parsed)}${token.slice(closingBrace + 1)}`
102
+ }
103
+
104
+ /**
105
+ * Interpolating the variable reference starting with a
106
+ * `$`. We only capture numbers,letter and underscore.
107
+ * For other characters, one can use the mustache
108
+ * braces.
109
+ */
110
+ #interpolateVariable(token: string, parsed: any) {
111
+ return token.replace(/[a-zA-Z0-9_]+/, (key) => {
112
+ return this.#getValue(key, parsed)
113
+ })
114
+ }
115
+
116
+ /**
117
+ * Interpolates the referenced values
118
+ */
119
+ #interpolate(value: string, parsed: DotenvParseOutput): string {
120
+ const tokens = value.split('$')
121
+
122
+ let newValue = ''
123
+ let skipNextToken = true
124
+
125
+ tokens.forEach((token) => {
126
+ /**
127
+ * If the value is an escaped sequence, then we replace it
128
+ * with a `$` and then skip the next token.
129
+ */
130
+ if (token === '\\') {
131
+ newValue += '$'
132
+ skipNextToken = true
133
+ return
134
+ }
135
+
136
+ /**
137
+ * Use the value as it is when "skipNextToken" is set to true.
138
+ */
139
+ if (skipNextToken) {
140
+ /**
141
+ * Replace the ending escape sequence with a $
142
+ */
143
+ newValue += token.replace(/\\$/, '$')
144
+ /**
145
+ * and then skip the next token if it ends with escape sequence
146
+ */
147
+ if (token.endsWith('\\')) {
148
+ return
149
+ }
150
+ } else {
151
+ /**
152
+ * Handle mustache block
153
+ */
154
+ if (token.startsWith('{')) {
155
+ newValue += this.#interpolateMustache(token, parsed)
156
+ return
157
+ }
158
+
159
+ /**
160
+ * Process all words as variable
161
+ */
162
+ newValue += this.#interpolateVariable(token, parsed)
163
+ }
164
+
165
+ /**
166
+ * Process next token
167
+ */
168
+ skipNextToken = false
169
+ })
170
+
171
+ return newValue
172
+ }
173
+
174
+ /**
175
+ * Parse the env string to an object of environment variables.
176
+ */
177
+ parse(): DotenvParseOutput {
178
+ const envCollection = dotenv.parse(this.#envContents.trim())
179
+
180
+ return Object.keys(envCollection).reduce<DotenvParseOutput>((result, key) => {
181
+ result[key] = this.#getValue(key, envCollection)
182
+ return result
183
+ }, {})
184
+ }
185
+ }
@@ -0,0 +1,83 @@
1
+ /*
2
+ * @adonisjs/application
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import debug from './debug.js'
11
+ import { EnvParser } from './parser.js'
12
+ import { EnvLoader } from './loader.js'
13
+
14
+ /**
15
+ * Env processors loads, parses and process environment variables.
16
+ */
17
+ export class EnvProcessor {
18
+ /**
19
+ * App root is needed to load files
20
+ */
21
+ #appRoot: URL
22
+
23
+ constructor(appRoot: URL) {
24
+ this.#appRoot = appRoot
25
+ }
26
+
27
+ /**
28
+ * Parse env variables from raw contents
29
+ */
30
+ #processContents(envContents: string, store: Record<string, any>) {
31
+ /**
32
+ * Collected env variables
33
+ */
34
+ if (!envContents.trim()) {
35
+ return store
36
+ }
37
+
38
+ const values = new EnvParser(envContents).parse()
39
+ Object.keys(values).forEach((key) => {
40
+ let value = process.env[key]
41
+
42
+ if (!value) {
43
+ value = values[key]
44
+ process.env[key] = values[key]
45
+ }
46
+
47
+ if (!store[key]) {
48
+ store[key] = value
49
+ }
50
+ })
51
+
52
+ return store
53
+ }
54
+
55
+ /**
56
+ * Parse env variables by loading dot files.
57
+ */
58
+ async #loadAndProcessDotFiles() {
59
+ const loader = new EnvLoader(this.#appRoot)
60
+ const envFiles = await loader.load()
61
+
62
+ if (debug.enabled) {
63
+ debug(
64
+ 'processing .env files (priority from top to bottom) %O',
65
+ envFiles.map((file) => file.path)
66
+ )
67
+ }
68
+
69
+ /**
70
+ * Collected env variables
71
+ */
72
+ const envValues: Record<string, any> = {}
73
+ envFiles.forEach(({ contents }) => this.#processContents(contents, envValues))
74
+ return envValues
75
+ }
76
+
77
+ /**
78
+ * Process env variables
79
+ */
80
+ async process() {
81
+ return this.#loadAndProcessDotFiles()
82
+ }
83
+ }
@@ -0,0 +1,63 @@
1
+ /*
2
+ * @adonisjs/env
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import type { Exception } from '@poppinss/utils'
11
+ import { ValidateFn } from '@poppinss/validator-lite'
12
+
13
+ import { E_INVALID_ENV_VARIABLES } from './exceptions.js'
14
+
15
+ /**
16
+ * Exposes the API to validate environment variables against a
17
+ * pre-defined schema.
18
+ *
19
+ * The class is not exported in the main API and used internally.
20
+ */
21
+ export class EnvValidator<Schema extends { [key: string]: ValidateFn<unknown> }> {
22
+ #schema: Schema
23
+ #error: Exception
24
+
25
+ constructor(schema: Schema) {
26
+ this.#schema = schema
27
+ this.#error = new E_INVALID_ENV_VARIABLES()
28
+ }
29
+
30
+ /**
31
+ * Accepts an object of values to validate against the pre-defined
32
+ * schema.
33
+ *
34
+ * The return value is a merged copy of the original object and the
35
+ * values mutated by the schema validator.
36
+ */
37
+ validate(values: { [K: string]: string | undefined }): {
38
+ [K in keyof Schema]: ReturnType<Schema[K]>
39
+ } {
40
+ const help: string[] = []
41
+
42
+ const validated = Object.keys(this.#schema).reduce(
43
+ (result, key) => {
44
+ const value = process.env[key] || values[key]
45
+
46
+ try {
47
+ result[key] = this.#schema[key](key, value) as any
48
+ } catch (error) {
49
+ help.push(`- ${error.message}`)
50
+ }
51
+ return result
52
+ },
53
+ { ...values }
54
+ ) as { [K in keyof Schema]: ReturnType<Schema[K]> }
55
+
56
+ if (help.length) {
57
+ this.#error.help = help.join('\n')
58
+ throw this.#error
59
+ }
60
+
61
+ return validated
62
+ }
63
+ }