@adonisjs/env 4.2.0-1 → 4.2.0-3

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.
@@ -1,4 +1,52 @@
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
+ */
1
9
  import dotenv from 'dotenv';
10
+ /**
11
+ * Env parser parses the environment variables from a string formatted
12
+ * as a key-value pair seperated using an `=`. For example:
13
+ *
14
+ * ```dotenv
15
+ * PORT=3333
16
+ * HOST=127.0.0.1
17
+ * ```
18
+ *
19
+ * The variables can reference other environment variables as well using `$`.
20
+ * For example:
21
+ *
22
+ * ```dotenv
23
+ * PORT=3333
24
+ * REDIS_PORT=$PORT
25
+ * ```
26
+ *
27
+ * The variables using characters other than letters can wrap variable
28
+ * named inside a curly brace.
29
+ *
30
+ * ```dotenv
31
+ * APP-PORT=3333
32
+ * REDIS_PORT=${APP-PORT}
33
+ * ```
34
+ *
35
+ * You can escape the `$` sign with a backtick.
36
+ *
37
+ * ```dotenv
38
+ * REDIS_PASSWORD=foo\$123
39
+ * ```
40
+ *
41
+ * ## Usage
42
+ *
43
+ * ```ts
44
+ * const parser = new EnvParser(envContents)
45
+ * const output = parser.parse()
46
+ *
47
+ * // The output is a key-value pair
48
+ * ```
49
+ */
2
50
  export class EnvParser {
3
51
  #envContents;
4
52
  #preferProcessEnv = true;
@@ -8,6 +56,9 @@ export class EnvParser {
8
56
  }
9
57
  this.#envContents = envContents;
10
58
  }
59
+ /**
60
+ * Returns the value from the parsed object
61
+ */
11
62
  #getValue(key, parsed) {
12
63
  if (this.#preferProcessEnv && process.env[key]) {
13
64
  return process.env[key];
@@ -17,46 +68,94 @@ export class EnvParser {
17
68
  }
18
69
  return process.env[key] || '';
19
70
  }
71
+ /**
72
+ * Interpolating the token wrapped inside the mustache braces.
73
+ */
20
74
  #interpolateMustache(token, parsed) {
75
+ /**
76
+ * Finding the closing brace. If closing brace is missing, we
77
+ * consider the block as a normal string
78
+ */
21
79
  const closingBrace = token.indexOf('}');
22
80
  if (closingBrace === -1) {
23
81
  return token;
24
82
  }
83
+ /**
84
+ * Then we pull everything until the closing brace, except
85
+ * the opening brace and trim off all white spaces.
86
+ */
25
87
  const varReference = token.slice(1, closingBrace).trim();
88
+ /**
89
+ * Getting the value of the reference inside the braces
90
+ */
26
91
  return `${this.#getValue(varReference, parsed)}${token.slice(closingBrace + 1)}`;
27
92
  }
93
+ /**
94
+ * Interpolating the variable reference starting with a
95
+ * `$`. We only capture numbers,letter and underscore.
96
+ * For other characters, one can use the mustache
97
+ * braces.
98
+ */
28
99
  #interpolateVariable(token, parsed) {
29
100
  return token.replace(/[a-zA-Z0-9_]+/, (key) => {
30
101
  return this.#getValue(key, parsed);
31
102
  });
32
103
  }
104
+ /**
105
+ * Interpolates the referenced values
106
+ */
33
107
  #interpolate(value, parsed) {
34
108
  const tokens = value.split('$');
35
109
  let newValue = '';
36
110
  let skipNextToken = true;
37
111
  tokens.forEach((token) => {
112
+ /**
113
+ * If the value is an escaped sequence, then we replace it
114
+ * with a `$` and then skip the next token.
115
+ */
38
116
  if (token === '\\') {
39
117
  newValue += '$';
40
118
  skipNextToken = true;
41
119
  return;
42
120
  }
121
+ /**
122
+ * Use the value as it is when "skipNextToken" is set to true.
123
+ */
43
124
  if (skipNextToken) {
125
+ /**
126
+ * Replace the ending escape sequence with a $
127
+ */
44
128
  newValue += token.replace(/\\$/, '$');
129
+ /**
130
+ * and then skip the next token if it ends with escape sequence
131
+ */
45
132
  if (token.endsWith('\\')) {
46
133
  return;
47
134
  }
48
135
  }
49
136
  else {
137
+ /**
138
+ * Handle mustache block
139
+ */
50
140
  if (token.startsWith('{')) {
51
141
  newValue += this.#interpolateMustache(token, parsed);
52
142
  return;
53
143
  }
144
+ /**
145
+ * Process all words as variable
146
+ */
54
147
  newValue += this.#interpolateVariable(token, parsed);
55
148
  }
149
+ /**
150
+ * Process next token
151
+ */
56
152
  skipNextToken = false;
57
153
  });
58
154
  return newValue;
59
155
  }
156
+ /**
157
+ * Parse the env string to an object of environment variables.
158
+ */
60
159
  parse() {
61
160
  const envCollection = dotenv.parse(this.#envContents.trim());
62
161
  return Object.keys(envCollection).reduce((result, key) => {
@@ -1,6 +1,12 @@
1
- /// <reference types="node" resolution-mode="require"/>
1
+ /// <reference types="@types/node" resolution-mode="require"/>
2
+ /**
3
+ * Env processors loads, parses and process environment variables.
4
+ */
2
5
  export declare class EnvProcessor {
3
6
  #private;
4
7
  constructor(appRoot: URL);
8
+ /**
9
+ * Process env variables
10
+ */
5
11
  process(): Promise<Record<string, any>>;
6
12
  }
@@ -1,12 +1,32 @@
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
+ */
1
9
  import debug from './debug.js';
2
10
  import { EnvParser } from './parser.js';
3
11
  import { EnvLoader } from './loader.js';
12
+ /**
13
+ * Env processors loads, parses and process environment variables.
14
+ */
4
15
  export class EnvProcessor {
16
+ /**
17
+ * App root is needed to load files
18
+ */
5
19
  #appRoot;
6
20
  constructor(appRoot) {
7
21
  this.#appRoot = appRoot;
8
22
  }
23
+ /**
24
+ * Parse env variables from raw contents
25
+ */
9
26
  #processContents(envContents, store) {
27
+ /**
28
+ * Collected env variables
29
+ */
10
30
  if (!envContents.trim()) {
11
31
  return store;
12
32
  }
@@ -23,16 +43,25 @@ export class EnvProcessor {
23
43
  });
24
44
  return store;
25
45
  }
46
+ /**
47
+ * Parse env variables by loading dot files.
48
+ */
26
49
  async #loadAndProcessDotFiles() {
27
50
  const loader = new EnvLoader(this.#appRoot);
28
51
  const envFiles = await loader.load();
29
52
  if (debug.enabled) {
30
53
  debug('processing .env files (priority from top to bottom) %O', envFiles.map((file) => file.path));
31
54
  }
55
+ /**
56
+ * Collected env variables
57
+ */
32
58
  const envValues = {};
33
59
  envFiles.forEach(({ contents }) => this.#processContents(contents, envValues));
34
60
  return envValues;
35
61
  }
62
+ /**
63
+ * Process env variables
64
+ */
36
65
  async process() {
37
66
  return this.#loadAndProcessDotFiles();
38
67
  }
@@ -1,9 +1,22 @@
1
1
  import { ValidateFn } from '@poppinss/validator-lite';
2
+ /**
3
+ * Exposes the API to validate environment variables against a
4
+ * pre-defined schema.
5
+ *
6
+ * The class is not exported in the main API and used internally.
7
+ */
2
8
  export declare class EnvValidator<Schema extends {
3
9
  [key: string]: ValidateFn<unknown>;
4
10
  }> {
5
11
  #private;
6
12
  constructor(schema: Schema);
13
+ /**
14
+ * Accepts an object of values to validate against the pre-defined
15
+ * schema.
16
+ *
17
+ * The return value is a merged copy of the original object and the
18
+ * values mutated by the schema validator.
19
+ */
7
20
  validate(values: {
8
21
  [K: string]: string | undefined;
9
22
  }): {
@@ -1,4 +1,18 @@
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
+ */
1
9
  import { E_INVALID_ENV_VARIABLES } from './exceptions.js';
10
+ /**
11
+ * Exposes the API to validate environment variables against a
12
+ * pre-defined schema.
13
+ *
14
+ * The class is not exported in the main API and used internally.
15
+ */
2
16
  export class EnvValidator {
3
17
  #schema;
4
18
  #error;
@@ -6,6 +20,13 @@ export class EnvValidator {
6
20
  this.#schema = schema;
7
21
  this.#error = new E_INVALID_ENV_VARIABLES();
8
22
  }
23
+ /**
24
+ * Accepts an object of values to validate against the pre-defined
25
+ * schema.
26
+ *
27
+ * The return value is a merged copy of the original object and the
28
+ * values mutated by the schema validator.
29
+ */
9
30
  validate(values) {
10
31
  const help = [];
11
32
  const validated = Object.keys(this.#schema).reduce((result, key) => {
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,21 +1,28 @@
1
1
  {
2
2
  "name": "@adonisjs/env",
3
- "version": "4.2.0-1",
3
+ "version": "4.2.0-3",
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": {
13
16
  ".": "./build/index.js"
14
17
  },
18
+ "engines": {
19
+ "node": ">=18.16.0"
20
+ },
15
21
  "scripts": {
16
22
  "pretest": "npm run lint",
17
- "test": "cross-env NODE_DEBUG=adonisjs:env c8 npm run vscode:test",
23
+ "test": "cross-env NODE_DEBUG=adonisjs:env c8 npm run quick:test",
18
24
  "clean": "del-cli build",
25
+ "typecheck": "tsc --noEmit",
19
26
  "compile": "npm run lint && npm run clean && tsc",
20
27
  "build": "npm run compile",
21
28
  "release": "np",
@@ -24,7 +31,7 @@
24
31
  "lint": "eslint . --ext=.ts",
25
32
  "format": "prettier --write .",
26
33
  "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/env",
27
- "vscode:test": "node --loader=ts-node/esm bin/test.ts"
34
+ "quick:test": "node --loader=ts-node/esm bin/test.ts"
28
35
  },
29
36
  "keywords": [
30
37
  "env",
@@ -33,37 +40,32 @@
33
40
  "author": "virk,adonisjs",
34
41
  "license": "MIT",
35
42
  "devDependencies": {
36
- "@commitlint/cli": "^17.4.4",
37
- "@commitlint/config-conventional": "^17.4.4",
38
- "@japa/assert": "^1.4.1",
39
- "@japa/expect-type": "^1.0.3",
40
- "@japa/file-system": "^1.0.1",
41
- "@japa/run-failed-tests": "^1.1.1",
42
- "@japa/runner": "^2.5.1",
43
- "@japa/spec-reporter": "^1.3.3",
44
- "@poppinss/dev-utils": "^2.0.3",
45
- "@swc/core": "^1.3.40",
46
- "@types/fs-extra": "^11.0.1",
47
- "@types/node": "^18.15.3",
48
- "c8": "^7.13.0",
43
+ "@adonisjs/eslint-config": "^1.1.7",
44
+ "@adonisjs/prettier-config": "^1.1.7",
45
+ "@adonisjs/tsconfig": "^1.1.7",
46
+ "@commitlint/cli": "^17.6.6",
47
+ "@commitlint/config-conventional": "^17.6.6",
48
+ "@japa/assert": "^2.0.0-1",
49
+ "@japa/expect-type": "^2.0.0-0",
50
+ "@japa/file-system": "^2.0.0-1",
51
+ "@japa/runner": "^3.0.0-3",
52
+ "@swc/core": "^1.3.67",
53
+ "@types/node": "^20.3.3",
54
+ "c8": "^8.0.0",
49
55
  "cross-env": "^7.0.3",
50
56
  "del-cli": "^5.0.0",
51
- "eslint": "^8.36.0",
52
- "eslint-config-prettier": "^8.7.0",
53
- "eslint-plugin-adonis": "^3.0.3",
54
- "eslint-plugin-prettier": "^4.2.1",
55
- "fs-extra": "^11.1.0",
57
+ "eslint": "^8.44.0",
56
58
  "github-label-sync": "^2.3.1",
57
59
  "husky": "^8.0.3",
58
- "np": "^7.6.3",
59
- "prettier": "^2.8.4",
60
+ "np": "^8.0.4",
61
+ "prettier": "^2.8.8",
60
62
  "ts-node": "^10.9.1",
61
- "typescript": "^4.9.5"
63
+ "typescript": "^5.1.6"
62
64
  },
63
65
  "dependencies": {
64
- "@poppinss/utils": "^6.5.0-1",
66
+ "@poppinss/utils": "^6.5.0-3",
65
67
  "@poppinss/validator-lite": "^1.0.3",
66
- "dotenv": "^16.0.3",
68
+ "dotenv": "^16.3.1",
67
69
  "split-lines": "^3.0.0"
68
70
  },
69
71
  "repository": {
@@ -74,36 +76,6 @@
74
76
  "url": "https://github.com/adonisjs/env/issues"
75
77
  },
76
78
  "homepage": "https://github.com/adonisjs/env#readme",
77
- "eslintConfig": {
78
- "extends": [
79
- "plugin:adonis/typescriptPackage",
80
- "prettier"
81
- ],
82
- "plugins": [
83
- "prettier"
84
- ],
85
- "rules": {
86
- "prettier/prettier": [
87
- "error",
88
- {
89
- "endOfLine": "auto"
90
- }
91
- ]
92
- }
93
- },
94
- "eslintIgnore": [
95
- "build"
96
- ],
97
- "prettier": {
98
- "trailingComma": "es5",
99
- "semi": false,
100
- "singleQuote": true,
101
- "useTabs": false,
102
- "quoteProps": "consistent",
103
- "bracketSpacing": true,
104
- "arrowParens": "always",
105
- "printWidth": 100
106
- },
107
79
  "commitlint": {
108
80
  "extends": [
109
81
  "@commitlint/config-conventional"
@@ -127,5 +99,9 @@
127
99
  "exclude": [
128
100
  "tests/**"
129
101
  ]
130
- }
102
+ },
103
+ "eslintConfig": {
104
+ "extends": "@adonisjs/eslint-config/package"
105
+ },
106
+ "prettier": "@adonisjs/prettier-config"
131
107
  }
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
+ }