├── .gitignore ├── .npmrc ├── .nvmrc ├── .vscode ├── memo.md └── settings.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── package.json ├── packages ├── cli │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── __test__ │ │ ├── cli.test.ts │ │ └── env │ │ │ ├── .env │ │ │ ├── mul.env │ │ │ ├── substitutions.env │ │ │ └── yml.env │ ├── index.ts │ ├── package.json │ ├── rollup.config.js │ └── tsconfig.json └── core │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── __test__ │ ├── common.test.ts │ ├── env │ │ ├── .env │ │ ├── mul.env │ │ ├── substitutions.env │ │ └── yml.env │ ├── exec.test.ts │ ├── jsfile │ │ ├── .env │ │ └── hello.js │ ├── load.test.ts │ ├── marshal.test.ts │ ├── parse.test.ts │ └── write.test.ts │ ├── common.ts │ ├── index.ts │ ├── package.json │ ├── parser.ts │ ├── rollup.config.js │ ├── tsconfig.json │ ├── types.ts │ └── utils.ts ├── rollup.config.js ├── rollup.utils.js ├── scripts ├── build.ts ├── publish.ts ├── tsconfig.json ├── utils.ts └── version.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | coverage/ 4 | build/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.vscode/memo.md: -------------------------------------------------------------------------------- 1 | // some maybe useful code segment 2 | 3 | ```ts 4 | lines 5 | .replaceAll(LINE, "") 6 | .split(/[\n\r]+/) 7 | .forEach((line) => { 8 | this.parseLine(line); 9 | }); 10 | ``` 11 | 12 | ```ts 13 | private parseLine(line: string) { 14 | const exportRegex = /^\s*(?:export\s+)?(.*?)\s*$/g; 15 | if (line.length === 0) { 16 | throw new Error("zero length string"); 17 | } 18 | 19 | // ditch the comments (but keep quoted hashes) 20 | if (line.includes("#")) { 21 | const segmentsBetweenHashed = line.split("#"); 22 | let quotesAreOpen = false; 23 | const segmentsToKeep: string[] = []; 24 | for (const segment of segmentsBetweenHashed) { 25 | if (count(segment, '"') === 1 || count(segment, "'") === 1) { 26 | if (quotesAreOpen) { 27 | quotesAreOpen = false; 28 | segmentsToKeep.push(segment); 29 | } else { 30 | quotesAreOpen = true; 31 | } 32 | } 33 | 34 | if (segmentsToKeep.length === 0 || quotesAreOpen) { 35 | segmentsToKeep.push(segment); 36 | } 37 | } 38 | line = segmentsToKeep.join("#"); 39 | } 40 | 41 | const firstEquals = line.indexOf("="); 42 | const firstColon = line.indexOf(":"); 43 | let splitString = splitN(line, "=", 2); 44 | if (firstColon != -1 && (firstColon < firstEquals || firstEquals === -1)) { 45 | // this is a yaml-style line 46 | splitString = splitN(line, ":", 2); 47 | } 48 | 49 | if (splitString.length !== 2) { 50 | console.log(this.map); 51 | console.log(line, splitString); 52 | throw new Error("Can't separate key from value"); 53 | } 54 | 55 | // Parse the key 56 | const key = splitString[0].replaceAll(exportRegex, "$1"); 57 | // Parse the value 58 | const value = this.parseValue(splitString[1]); 59 | return [key, value]; 60 | } 61 | ``` 62 | 63 | ```ts 64 | export const splitN = (str: string, sep: string, n: number) => { 65 | const res: string[] = []; 66 | let i = 0; 67 | let index = 0; 68 | n--; 69 | while (n != 0) { 70 | index = str.indexOf(sep, i); 71 | if (index < 0) { 72 | break; 73 | } 74 | res.push(str.substring(0, index)); 75 | str = str.substring(index + sep.length); 76 | n--; 77 | } 78 | res.push(str); 79 | return res; 80 | }; 81 | 82 | export function count(s: string, substr: string): number { 83 | return s.match(new RegExp(substr, "g"))?.length || 0; 84 | } 85 | 86 | it("splitN", () => { 87 | const str = "a#bc#da#dsad#"; 88 | const splitStr1 = splitN(str, "#", 1); 89 | const splitStr3 = splitN(str, "#", 3); 90 | const splitStr2 = splitN(str, "#", 2); 91 | const splitStr5 = splitN(str, "#", 5); 92 | expect(splitStr1).toStrictEqual(["a#bc#da#dsad#"]); 93 | expect(splitStr2).toStrictEqual(["a", "bc#da#dsad#"]); 94 | expect(splitStr3).toStrictEqual(["a", "bc", "da#dsad#"]); 95 | expect(splitStr5).toStrictEqual(["a", "bc", "da", "dsad", ""]); 96 | }); 97 | ``` 98 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.tabSize": 2, 5 | "typescript.tsdk": "node_modules/typescript/lib" 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | - [core](packages/core/CHANGELOG.md) 4 | - [cli](packages/cli/CHANGELOG.md) 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 Dotenv Software Inc. 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dotenv 2 | 3 | Written in typescript, full testing. 4 | 5 | It can loads environment variables from a `.env` file into [`process.env`](https://nodejs.org/docs/latest/api/process.html#process_process_env) or parse `=` string 6 | 7 | ## Installation 8 | 9 | ```shell 10 | npm i @jsdotenv/core 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Load env file 16 | 17 | Add your application configuration to your `.env` file in the root of your project: 18 | 19 | ```shell 20 | S3_BUCKET=YOURS3BUCKET 21 | SECRET_KEY=YOURSECRETKEYGOESHERE 22 | ``` 23 | 24 | Then in your Nodejs app you can do something like 25 | 26 | ```ts 27 | import dotenv from "@jsdotenv/core"; 28 | 29 | dotenv.load([__dirname + "/.env"]); 30 | console.log(process.env["S3_BUCKET"]); 31 | ``` 32 | 33 | If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) 34 | 35 | ```shell 36 | # I am a comment and that is OK 37 | SOME_VAR=someval 38 | FOO=BAR # comments at line end are OK too 39 | export BAR=BAZ 40 | ``` 41 | 42 | Or finally you can do YAML(ish) style 43 | 44 | ```yaml 45 | FOO: bar 46 | BAR: baz 47 | ``` 48 | 49 | Multiple line is OK. 50 | 51 | ```shell 52 | MULTI_DOUBLE_QUOTED="THIS 53 | IS 54 | A 55 | MULTILINE 56 | STRING" 57 | ``` 58 | 59 | Expand variables is OK. 60 | 61 | ```shellOPTION_A=1 62 | OPTION_B=${OPTION_A} 63 | OPTION_C=$OPTION_B 64 | OPTION_D=${OPTION_A}${OPTION_B} 65 | OPTION_E=${OPTION_NOT_DEFINED} 66 | ``` 67 | 68 | ### Writing Env Files 69 | 70 | dotenv can also write a map representing the environment to a correctly-formatted and escaped file. 71 | 72 | ```ts 73 | const map = new Map(); 74 | map.set("BASIC", "basic"); 75 | map.set("KEY", "value"); 76 | const filepath = path.resolve(__dirname, "./jsfile/.env"); 77 | dotenv.write(map, filepath); 78 | ``` 79 | 80 | ... or to a string 81 | 82 | ```ts 83 | const map = new Map(); 84 | map.set("BASIC", "basic"); 85 | map.set("KEY", "value"); 86 | const lines = dotenv.marshal(map); 87 | ``` 88 | 89 | ### Exec commands 90 | 91 | dotenv can run commands and inherit output. 92 | 93 | exec bash commands 94 | 95 | ```ts 96 | const pathname = path.resolve(__dirname + "/env/.env"); 97 | const out = dotenv.exec([pathname], "bash", ["-c", 'echo "$BASIC"']); 98 | ``` 99 | 100 | exec nodejs commands 101 | 102 | ```ts 103 | const pathname = path.resolve(__dirname, "./jsfile/hello.js"); 104 | const out = dotenv.exec([], "node", [pathname]); 105 | ``` 106 | 107 | ## Cli 108 | 109 | ```shell 110 | npm i create-dotenv -g 111 | ``` 112 | 113 | ### Usage 114 | 115 | Execute commands using key-value pairs. 116 | 117 | ```shell 118 | dotenv-cli -v KEY=VALUE -- bash -c 'echo "$KEY"' 119 | ``` 120 | 121 | Execute commands using enviroment file. 122 | 123 | ```shell 124 | dotenv-cli -e .env -- bash -c 'echo "$BASIC"' 125 | ``` 126 | 127 | Execute commands --help for Usage 128 | 129 | ```shell 130 | dotenv-cli --help 131 | ``` 132 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dotenv-monorepo", 3 | "private": "true", 4 | "license": "MIT", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "engines": { 9 | "node": ">=14" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.16.0", 13 | "@babel/preset-env": "^7.16.4", 14 | "@babel/preset-typescript": "^7.16.0", 15 | "@rollup/plugin-babel": "^5.3.1", 16 | "@rollup/plugin-commonjs": "^22.0.2", 17 | "@rollup/plugin-node-resolve": "^13.3.0", 18 | "@types/cross-spawn": "^6.0.2", 19 | "@types/envinfo": "^7.8.1", 20 | "@types/jest": "^27.0.3", 21 | "@types/jsonfile": "^6.1.0", 22 | "@types/node": "^18.7.6", 23 | "@types/semver": "^7.3.12", 24 | "babel-jest": "^27.4.4", 25 | "cross-spawn": "^7.0.3", 26 | "del": "^7.0.0", 27 | "esbuild": "^0.15.5", 28 | "esbuild-register": "^3.3.3", 29 | "jest": "^27.4.4", 30 | "jsonfile": "^6.1.0", 31 | "prompt-confirm": "^2.0.4", 32 | "rollup": "^2.78.0", 33 | "rollup-plugin-copy": "^3.4.0", 34 | "semver": "^7.3.7", 35 | "tslib": "^2.4.0", 36 | "typescript": "^4.7.4" 37 | }, 38 | "scripts": { 39 | "preinstall": "npx only-allow yarn", 40 | "build": "node -r esbuild-register scripts/build.ts", 41 | "test": "jest --coverage", 42 | "ver": "node -r esbuild-register scripts/version.ts", 43 | "pub": "node -r esbuild-register scripts/publish.ts" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## 2.0.8 (2022-08-29) 9 | 10 | ### Added 11 | 12 | - init cli 13 | - fully typescript 14 | - fully tested 15 | -------------------------------------------------------------------------------- /packages/cli/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 Dotenv Software Inc. 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # dotenv 2 | 3 | Written in typescript, full testing. 4 | 5 | It can loads environment variables from a `.env` file into [`process.env`](https://nodejs.org/docs/latest/api/process.html#process_process_env) or parse `=` string 6 | 7 | ## Installation 8 | 9 | ```shell 10 | npm i @jsdotenv/core 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Load env file 16 | 17 | Add your application configuration to your `.env` file in the root of your project: 18 | 19 | ```shell 20 | S3_BUCKET=YOURS3BUCKET 21 | SECRET_KEY=YOURSECRETKEYGOESHERE 22 | ``` 23 | 24 | Then in your Nodejs app you can do something like 25 | 26 | ```ts 27 | import dotenv from "@jsdotenv/core"; 28 | 29 | dotenv.load([__dirname + "/.env"]); 30 | console.log(process.env["S3_BUCKET"]); 31 | ``` 32 | 33 | If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) 34 | 35 | ```shell 36 | # I am a comment and that is OK 37 | SOME_VAR=someval 38 | FOO=BAR # comments at line end are OK too 39 | export BAR=BAZ 40 | ``` 41 | 42 | Or finally you can do YAML(ish) style 43 | 44 | ```yaml 45 | FOO: bar 46 | BAR: baz 47 | ``` 48 | 49 | Multiple line is OK. 50 | 51 | ```shell 52 | MULTI_DOUBLE_QUOTED="THIS 53 | IS 54 | A 55 | MULTILINE 56 | STRING" 57 | ``` 58 | 59 | Expand variables is OK. 60 | 61 | ```shellOPTION_A=1 62 | OPTION_B=${OPTION_A} 63 | OPTION_C=$OPTION_B 64 | OPTION_D=${OPTION_A}${OPTION_B} 65 | OPTION_E=${OPTION_NOT_DEFINED} 66 | ``` 67 | 68 | ### Writing Env Files 69 | 70 | dotenv can also write a map representing the environment to a correctly-formatted and escaped file. 71 | 72 | ```ts 73 | const map = new Map(); 74 | map.set("BASIC", "basic"); 75 | map.set("KEY", "value"); 76 | const filepath = path.resolve(__dirname, "./jsfile/.env"); 77 | dotenv.write(map, filepath); 78 | ``` 79 | 80 | ... or to a string 81 | 82 | ```ts 83 | const map = new Map(); 84 | map.set("BASIC", "basic"); 85 | map.set("KEY", "value"); 86 | const lines = dotenv.marshal(map); 87 | ``` 88 | 89 | ### Exec commands 90 | 91 | dotenv can run commands and inherit output. 92 | 93 | exec bash commands 94 | 95 | ```ts 96 | const pathname = path.resolve(__dirname + "/env/.env"); 97 | const out = dotenv.exec([pathname], "bash", ["-c", 'echo "$BASIC"']); 98 | ``` 99 | 100 | exec nodejs commands 101 | 102 | ```ts 103 | const pathname = path.resolve(__dirname, "./jsfile/hello.js"); 104 | const out = dotenv.exec([], "node", [pathname]); 105 | ``` 106 | 107 | ## Cli 108 | 109 | ```shell 110 | npm i create-dotenv -g 111 | ``` 112 | 113 | ### Usage 114 | 115 | Execute commands using key-value pairs. 116 | 117 | ```shell 118 | dotenv-cli -v KEY=VALUE -- bash -c 'echo "$KEY"' 119 | ``` 120 | 121 | Execute commands using enviroment file. 122 | 123 | ```shell 124 | dotenv-cli -e .env -- bash -c 'echo "$BASIC"' 125 | ``` 126 | 127 | Execute commands --help for Usage 128 | 129 | ```shell 130 | dotenv-cli --help 131 | ``` 132 | -------------------------------------------------------------------------------- /packages/cli/__test__/cli.test.ts: -------------------------------------------------------------------------------- 1 | import spawn from "cross-spawn"; 2 | import path from "path"; 3 | async function execDotenv(args: string[]) { 4 | const cp = spawn.sync("node", [ 5 | "-r", 6 | require.resolve("esbuild-register"), 7 | path.resolve(__dirname, "../index.ts"), 8 | ...args, 9 | ]); 10 | 11 | return { 12 | stdout: cp.stdout.toString("utf-8"), 13 | }; 14 | } 15 | 16 | describe("cli", () => { 17 | it("help", async () => { 18 | const { stdout } = await execDotenv(["--help"]); 19 | expect(stdout).toBe( 20 | "Usage: dotenv-cli [--help] [--debug] [-e ] [-v =] [-p ] [-- command]\n" + 21 | " --help print help\n" + 22 | " --debug output the files that would be processed but don't actually parse them or run the `command`\n" + 23 | " -e parses the file as a `.env` file and adds the variables to the environment\n" + 24 | " -e multiple -e flags are allowed\n" + 25 | " -v = put variable into environment using value \n" + 26 | " -v = multiple -v flags are allowed\n" + 27 | " -p print value of to the console. If you specify this, you do not have to specify a `command`\n" + 28 | " command `command` is the actual command you want to run. Best practice is to precede this command with ` -- `. Everything after `--` is considered to be your command. So any flags will not be parsed by this tool but be passed to your command. If you do not do it, this tool will strip those flags\n" 29 | ); 30 | }); 31 | 32 | it("-e", async () => { 33 | const { stdout } = await execDotenv([ 34 | "-e", 35 | path.resolve(__dirname, "./env/.env"), 36 | "--", 37 | "bash", 38 | "-c", 39 | 'echo "$BASIC"', 40 | ]); 41 | expect(stdout).toBe("basic\n"); 42 | }); 43 | 44 | it("-e mul", async () => { 45 | const { stdout } = await execDotenv([ 46 | "-e", 47 | path.resolve(__dirname, "./env/.env"), 48 | "-e", 49 | path.resolve(__dirname, "./env/yml.env"), 50 | "--", 51 | "bash", 52 | "-c", 53 | 'echo "$BASIC"', 54 | ]); 55 | expect(stdout).toBe("basic1\n"); 56 | }); 57 | 58 | it("-v", async () => { 59 | const { stdout } = await execDotenv([ 60 | "-v", 61 | "KEY=VALUE", 62 | "--", 63 | "bash", 64 | "-c", 65 | 'echo "$KEY"', 66 | ]); 67 | expect(stdout).toBe("VALUE\n"); 68 | }); 69 | 70 | it("-v mul", async () => { 71 | const { stdout } = await execDotenv([ 72 | "-v", 73 | "KEY=VALUE", 74 | "-v", 75 | "KEY1=VALUE1", 76 | "--", 77 | "bash", 78 | "-c", 79 | 'echo "$KEY" "$KEY1"', 80 | ]); 81 | expect(stdout).toBe("VALUE VALUE1\n"); 82 | }); 83 | 84 | it("-p", async () => { 85 | const { stdout } = await execDotenv([ 86 | "-v", 87 | "KEY=VALUE", 88 | "-p", 89 | "KEY", 90 | "--", 91 | "bash", 92 | "-c", 93 | 'echo "$KEY"', 94 | ]); 95 | expect(stdout).toBe("VALUE\nVALUE\n"); 96 | }); 97 | 98 | it("--debug", async () => { 99 | const { stdout } = await execDotenv([ 100 | "-v", 101 | "KEY=VALUE", 102 | "-e", 103 | "./env/substitutions.env", 104 | "--debug", 105 | ]); 106 | expect(stdout).toBe( 107 | "[ './env/substitutions.env' ]\nMap(1) { 'KEY' => 'VALUE' }\n" 108 | ); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /packages/cli/__test__/env/.env: -------------------------------------------------------------------------------- 1 | 2 | 3 | BASIC=basic 4 | 5 | # previous line intentionally left blank 6 | AFTER_LINE=after_line 7 | EMPTY= 8 | EMPTY_SINGLE_QUOTES='' 9 | EMPTY_DOUBLE_QUOTES="" 10 | EMPTY_BACKTICKS=`` 11 | SINGLE_QUOTES='single_quotes' 12 | SINGLE_QUOTES_SPACED=' single quotes ' 13 | DOUBLE_QUOTES="double_quotes" 14 | DOUBLE_QUOTES_SPACED=" double quotes " 15 | DOUBLE_QUOTES_INSIDE_SINGLE='double "quotes" work inside single quotes' 16 | DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET="{ port: $MONGOLAB_PORT}" 17 | SINGLE_QUOTES_INSIDE_DOUBLE="single 'quotes' work inside double quotes" 18 | BACKTICKS_INSIDE_SINGLE='`backticks` work inside single quotes' 19 | BACKTICKS_INSIDE_DOUBLE="`backticks` work inside double quotes" 20 | BACKTICKS=`backticks` 21 | BACKTICKS_SPACED=` backticks ` 22 | DOUBLE_QUOTES_INSIDE_BACKTICKS=`double "quotes" work inside backticks` 23 | SINGLE_QUOTES_INSIDE_BACKTICKS=`single 'quotes' work inside backticks` 24 | DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS=`double "quotes" and single 'quotes' work inside backticks` 25 | EXPAND_NEWLINES="expand\nnew\nlines\r\b" 26 | DONT_EXPAND_UNQUOTED=dontexpand\nnewlines 27 | DONT_EXPAND_SQUOTED='dontexpand\nnewlines' 28 | DONT_EXPAND_SQUOTED='dontexpand\nnewlines' 29 | # COMMENTS=work 30 | INLINE_COMMENTS=inline comments # work #very #well 31 | INLINE_COMMENTS_SINGLE_QUOTES='inline comments outside of #singlequotes' # work 32 | INLINE_COMMENTS_DOUBLE_QUOTES="inline comments outside of #doublequotes" # work 33 | INLINE_COMMENTS_BACKTICKS=`inline comments outside of #backticks` # work 34 | INLINE_COMMENTS_SPACE=inline comments start with a#number sign. no space required. 35 | EQUAL_SIGNS=equals== 36 | RETAIN_INNER_QUOTES={"foo": "bar"} 37 | RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}' 38 | RETAIN_INNER_QUOTES_AS_BACKTICKS=`{"foo": "bar's"}` 39 | TRIM_SPACE_FROM_UNQUOTED= some spaced out string 40 | USERNAME=therealnerdybeast@example.tld 41 | SPACED_KEY = parsed 42 | export OPTION_A='postgres://localhost:5432/database?sslmode=disable' -------------------------------------------------------------------------------- /packages/cli/__test__/env/mul.env: -------------------------------------------------------------------------------- 1 | BASIC=basic 2 | 3 | # previous line intentionally left blank 4 | AFTER_LINE=after_line 5 | EMPTY= 6 | SINGLE_QUOTES='single_quotes' 7 | SINGLE_QUOTES_SPACED=' single quotes ' 8 | DOUBLE_QUOTES="double_quotes" 9 | DOUBLE_QUOTES_SPACED=" double quotes " 10 | EXPAND_NEWLINES="expand\nnew\nlines" 11 | DONT_EXPAND_UNQUOTED=dontexpand\nnewlines 12 | DONT_EXPAND_SQUOTED='dontexpand\nnewlines' 13 | # COMMENTS=work 14 | EQUAL_SIGNS=equals== 15 | RETAIN_INNER_QUOTES={"foo": "bar"} 16 | 17 | RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}' 18 | TRIM_SPACE_FROM_UNQUOTED= some spaced out string 19 | USERNAME=therealnerdybeast@example.tld 20 | SPACED_KEY = parsed 21 | 22 | MULTI_DOUBLE_QUOTED="THIS 23 | IS 24 | A 25 | MULTILINE 26 | STRING" 27 | 28 | MULTI_SINGLE_QUOTED='THIS 29 | IS 30 | A 31 | MULTILINE 32 | STRING' 33 | 34 | MULTI_BACKTICKED=`THIS 35 | IS 36 | A 37 | "MULTILINE'S" 38 | STRING` -------------------------------------------------------------------------------- /packages/cli/__test__/env/substitutions.env: -------------------------------------------------------------------------------- 1 | OPTION_A=1 2 | OPTION_B=${OPTION_A} 3 | OPTION_C=$OPTION_B 4 | OPTION_D=${OPTION_A}${OPTION_B} 5 | OPTION_E=${OPTION_NOT_DEFINED} 6 | -------------------------------------------------------------------------------- /packages/cli/__test__/env/yml.env: -------------------------------------------------------------------------------- 1 | 2 | 3 | BASIC: basic1 4 | 5 | # previous line intentionally left blank 6 | AFTER_LINE: after_line 7 | EMPTY: 8 | EMPTY_SINGLE_QUOTES: '' 9 | EMPTY_DOUBLE_QUOTES: "" 10 | EMPTY_BACKTICKS: `` 11 | SINGLE_QUOTES: 'single_quotes' 12 | SINGLE_QUOTES_SPACED: ' single quotes ' 13 | DOUBLE_QUOTES: "double_quotes" 14 | DOUBLE_QUOTES_SPACED: " double quotes " 15 | DOUBLE_QUOTES_INSIDE_SINGLE: 'double "quotes" work inside single quotes' 16 | DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET: "{ port: $MONGOLAB_PORT}" 17 | SINGLE_QUOTES_INSIDE_DOUBLE: "single 'quotes' work inside double quotes" 18 | BACKTICKS_INSIDE_SINGLE: '`backticks` work inside single quotes' 19 | BACKTICKS_INSIDE_DOUBLE: "`backticks` work inside double quotes" 20 | BACKTICKS: `backticks` 21 | BACKTICKS_SPACED: ` backticks ` 22 | DOUBLE_QUOTES_INSIDE_BACKTICKS: `double "quotes" work inside backticks` 23 | SINGLE_QUOTES_INSIDE_BACKTICKS: `single 'quotes' work inside backticks` 24 | DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS: `double "quotes" and single 'quotes' work inside backticks` 25 | EXPAND_NEWLINES: "expand\nnew\nlines\r\b" 26 | DONT_EXPAND_UNQUOTED: dontexpand\nnewlines 27 | DONT_EXPAND_SQUOTED: 'dontexpand\nnewlines' 28 | DONT_EXPAND_SQUOTED: 'dontexpand\nnewlines' 29 | # COMMENTS:work 30 | INLINE_COMMENTS: inline comments # work #very #well 31 | INLINE_COMMENTS_SINGLE_QUOTES: 'inline comments outside of #singlequotes' # work 32 | INLINE_COMMENTS_DOUBLE_QUOTES: "inline comments outside of #doublequotes" # work 33 | INLINE_COMMENTS_BACKTICKS: `inline comments outside of #backticks` # work 34 | INLINE_COMMENTS_SPACE: inline comments start with a#number sign. no space required. 35 | EQUAL_SIGNS: equals== 36 | RETAIN_INNER_QUOTES: {"foo": "bar"} 37 | RETAIN_INNER_QUOTES_AS_STRING: '{"foo": "bar"}' 38 | RETAIN_INNER_QUOTES_AS_BACKTICKS: `{"foo": "bar's"}` 39 | TRIM_SPACE_FROM_UNQUOTED: some spaced out string 40 | USERNAME: therealnerdybeast@example.tld 41 | SPACED_KEY: parsed 42 | export OPTION_A: 'postgres://localhost:5432/database?sslmode=disable' -------------------------------------------------------------------------------- /packages/cli/index.ts: -------------------------------------------------------------------------------- 1 | const minimist = require("minimist"); 2 | const spawn = require("cross-spawn"); 3 | import dotenv from "@jsdotenv/core"; 4 | 5 | function printHelp() { 6 | console.log( 7 | [ 8 | "Usage: dotenv-cli [--help] [--debug] [-e ] [-v =] [-p ] [-- command]", 9 | " --help print help", 10 | " --debug output the files that would be processed but don't actually parse them or run the `command`", 11 | " -e parses the file as a `.env` file and adds the variables to the environment", 12 | " -e multiple -e flags are allowed", 13 | " -v = put variable into environment using value ", 14 | " -v = multiple -v flags are allowed", 15 | " -p print value of to the console. If you specify this, you do not have to specify a `command`", 16 | " command `command` is the actual command you want to run. Best practice is to precede this command with ` -- `. Everything after `--` is considered to be your command. So any flags will not be parsed by this tool but be passed to your command. If you do not do it, this tool will strip those flags", 17 | ].join("\n") 18 | ); 19 | } 20 | 21 | const argv = minimist(process.argv.slice(2)); 22 | 23 | if (argv.help) { 24 | printHelp(); 25 | process.exit(); 26 | } 27 | 28 | let paths: string[] = []; 29 | if (argv.e) { 30 | if (typeof argv.e === "string") { 31 | paths.push(argv.e); 32 | } else { 33 | paths.push(...argv.e); 34 | } 35 | } 36 | 37 | function validateCmdVariable(param: string) { 38 | if (!param.match(/^\w+=[a-zA-Z0-9"=^!?%@_&\-/:;.]+$/)) { 39 | console.error( 40 | "Unexpected argument " + 41 | param + 42 | ". Expected variable in format variable=value" 43 | ); 44 | process.exit(1); 45 | } 46 | 47 | return param; 48 | } 49 | const variables = []; 50 | if (argv.v) { 51 | if (typeof argv.v === "string") { 52 | variables.push(validateCmdVariable(argv.v)); 53 | } else { 54 | variables.push(...argv.v.map(validateCmdVariable)); 55 | } 56 | } 57 | 58 | const parsedVariables = dotenv.parse( 59 | Buffer.from(variables.join("\n")).toString("utf-8") 60 | ); 61 | 62 | if (argv.debug) { 63 | console.log(paths); 64 | console.log(parsedVariables); 65 | process.exit(); 66 | } 67 | 68 | // exec after debug 69 | if (paths.length) { 70 | dotenv.load(paths, { override: true }); 71 | } 72 | 73 | for (const [key, value] of parsedVariables) { 74 | process.env[key] = value; 75 | } 76 | 77 | if (argv.p) { 78 | var value = process.env[argv.p]; 79 | console.log(value != null ? value : ""); 80 | } 81 | 82 | const command = argv._[0]; 83 | if (!command) { 84 | printHelp(); 85 | process.exit(1); 86 | } 87 | 88 | spawn(command, argv._.slice(1), { stdio: "inherit" }).on("exit", function( 89 | exitCode: number | null, 90 | signal: string 91 | ) { 92 | if (exitCode != null) { 93 | process.exit(exitCode); 94 | } else { 95 | process.kill(process.pid, signal); 96 | } 97 | }); 98 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-dotenv", 3 | "version": "2.0.9", 4 | "description": "Loads environment variables from .env file", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "bugs": { 9 | "url": "https://github.com/childrentime/dotenv/issues" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/childrentime/dotenv", 14 | "directory": "packages/cli" 15 | }, 16 | "sideEffects": false, 17 | "bin": { 18 | "dotenv-cli": "index.js" 19 | }, 20 | "license": "MIT", 21 | "engines": { 22 | "node": ">=14" 23 | }, 24 | "files": [ 25 | "dist/", 26 | "CHANGELOG.md", 27 | "LICENSE.md", 28 | "README.md" 29 | ], 30 | "keywords": [ 31 | "dotenv", 32 | "env", 33 | ".env", 34 | "environment", 35 | "variables", 36 | "config", 37 | "settings" 38 | ], 39 | "dependencies": { 40 | "@jsdotenv/core": "^2.0.9", 41 | "minimist": "^1.2.6", 42 | "cross-spawn": "^7.0.3" 43 | }, 44 | "devDependencies": { 45 | "@types/minimist": "^1.2.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/cli/rollup.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const babel = require("@rollup/plugin-babel").default; 3 | const nodeResolve = require("@rollup/plugin-node-resolve").default; 4 | const copy = require("rollup-plugin-copy"); 5 | const commonjs = require("@rollup/plugin-commonjs"); 6 | 7 | const { name: packageName } = require("./package.json"); 8 | const { getOutputDir } = require("../../rollup.utils"); 9 | 10 | module.exports = function rollup() { 11 | let sourceDir = "packages/cli"; 12 | let outputDir = getOutputDir(packageName); 13 | let outputDist = outputDir; 14 | return [ 15 | { 16 | input: `${sourceDir}/index.ts`, 17 | output: { 18 | dir: outputDist, 19 | format: "cjs", 20 | banner: "#!/usr/bin/env node\n", 21 | }, 22 | plugins: [ 23 | nodeResolve({ 24 | extensions: [".ts"], 25 | }), 26 | babel({ 27 | babelHelpers: "bundled", 28 | exclude: /node_modules/, 29 | extensions: [".ts"], 30 | }), 31 | commonjs(), 32 | copy({ 33 | targets: [ 34 | { src: `LICENSE.md`, dest: [outputDir, sourceDir] }, 35 | { src: `${sourceDir}/package.json`, dest: [outputDir] }, 36 | { src: `${sourceDir}/README.md`, dest: outputDir }, 37 | { src: `${sourceDir}/CHANGELOG.md`, dest: outputDir }, 38 | ], 39 | }), 40 | ], 41 | }, 42 | ]; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts", "rollup.config.js"], 3 | "exclude": ["dist", "__test__", "node_modules"], 4 | "compilerOptions": { 5 | "lib": ["es2021"], 6 | "module": "CommonJS", 7 | "target": "es2021", 8 | 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "moduleResolution": "node", 14 | "declaration": true, 15 | "emitDeclarationOnly": true, 16 | "rootDir": "." 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## 2.0.9 (2022-09-02) 9 | 10 | ### Added 11 | 12 | - support convert env map to lines or write to file 13 | - add exec method 14 | 15 | ## 2.0.8 (2022-08-29) 16 | 17 | ### Added 18 | 19 | - init core 20 | - support mul line, expand variables. 21 | - fully typescript 22 | - fully tested 23 | -------------------------------------------------------------------------------- /packages/core/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 Dotenv Software Inc. 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # dotenv 2 | 3 | Written in typescript, full testing. 4 | 5 | It can loads environment variables from a `.env` file into [`process.env`](https://nodejs.org/docs/latest/api/process.html#process_process_env) or parse `=` string 6 | 7 | ## Installation 8 | 9 | ```shell 10 | npm i @jsdotenv/core 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Load env file 16 | 17 | Add your application configuration to your `.env` file in the root of your project: 18 | 19 | ```shell 20 | S3_BUCKET=YOURS3BUCKET 21 | SECRET_KEY=YOURSECRETKEYGOESHERE 22 | ``` 23 | 24 | Then in your Nodejs app you can do something like 25 | 26 | ```ts 27 | import dotenv from "@jsdotenv/core"; 28 | 29 | dotenv.load([__dirname + "/.env"]); 30 | console.log(process.env["S3_BUCKET"]); 31 | ``` 32 | 33 | If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) 34 | 35 | ```shell 36 | # I am a comment and that is OK 37 | SOME_VAR=someval 38 | FOO=BAR # comments at line end are OK too 39 | export BAR=BAZ 40 | ``` 41 | 42 | Or finally you can do YAML(ish) style 43 | 44 | ```yaml 45 | FOO: bar 46 | BAR: baz 47 | ``` 48 | 49 | Multiple line is OK. 50 | 51 | ```shell 52 | MULTI_DOUBLE_QUOTED="THIS 53 | IS 54 | A 55 | MULTILINE 56 | STRING" 57 | ``` 58 | 59 | Expand variables is OK. 60 | 61 | ```shellOPTION_A=1 62 | OPTION_B=${OPTION_A} 63 | OPTION_C=$OPTION_B 64 | OPTION_D=${OPTION_A}${OPTION_B} 65 | OPTION_E=${OPTION_NOT_DEFINED} 66 | ``` 67 | 68 | ### Writing Env Files 69 | 70 | dotenv can also write a map representing the environment to a correctly-formatted and escaped file. 71 | 72 | ```ts 73 | const map = new Map(); 74 | map.set("BASIC", "basic"); 75 | map.set("KEY", "value"); 76 | const filepath = path.resolve(__dirname, "./jsfile/.env"); 77 | dotenv.write(map, filepath); 78 | ``` 79 | 80 | ... or to a string 81 | 82 | ```ts 83 | const map = new Map(); 84 | map.set("BASIC", "basic"); 85 | map.set("KEY", "value"); 86 | const lines = dotenv.marshal(map); 87 | ``` 88 | 89 | ### Exec commands 90 | 91 | dotenv can run commands and inherit output. 92 | 93 | exec bash commands 94 | 95 | ```ts 96 | const pathname = path.resolve(__dirname + "/env/.env"); 97 | const out = dotenv.exec([pathname], "bash", ["-c", 'echo "$BASIC"']); 98 | ``` 99 | 100 | exec nodejs commands 101 | 102 | ```ts 103 | const pathname = path.resolve(__dirname, "./jsfile/hello.js"); 104 | const out = dotenv.exec([], "node", [pathname]); 105 | ``` 106 | 107 | ## Cli 108 | 109 | ```shell 110 | npm i create-dotenv -g 111 | ``` 112 | 113 | ### Usage 114 | 115 | Execute commands using key-value pairs. 116 | 117 | ```shell 118 | dotenv-cli -v KEY=VALUE -- bash -c 'echo "$KEY"' 119 | ``` 120 | 121 | Execute commands using enviroment file. 122 | 123 | ```shell 124 | dotenv-cli -e .env -- bash -c 'echo "$BASIC"' 125 | ``` 126 | 127 | Execute commands --help for Usage 128 | 129 | ```shell 130 | dotenv-cli --help 131 | ``` 132 | -------------------------------------------------------------------------------- /packages/core/__test__/common.test.ts: -------------------------------------------------------------------------------- 1 | import { trimPrefix } from "../common"; 2 | describe("common", () => { 3 | it("trimPrefix", () => { 4 | const str = "export123"; 5 | const trimStr = trimPrefix(str, "export"); 6 | const noTrimStr = trimPrefix(str, "import"); 7 | expect(trimStr).toEqual("123"); 8 | expect(noTrimStr).toEqual("export123"); 9 | 10 | const escapeStr = `\\test`; 11 | const trimEscapeStr = trimPrefix(escapeStr, "\\"); 12 | expect(trimEscapeStr).toEqual("test"); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/core/__test__/env/.env: -------------------------------------------------------------------------------- 1 | 2 | 3 | BASIC=basic 4 | 5 | # previous line intentionally left blank 6 | AFTER_LINE=after_line 7 | EMPTY= 8 | EMPTY_SINGLE_QUOTES='' 9 | EMPTY_DOUBLE_QUOTES="" 10 | EMPTY_BACKTICKS=`` 11 | SINGLE_QUOTES='single_quotes' 12 | SINGLE_QUOTES_SPACED=' single quotes ' 13 | DOUBLE_QUOTES="double_quotes" 14 | DOUBLE_QUOTES_SPACED=" double quotes " 15 | DOUBLE_QUOTES_INSIDE_SINGLE='double "quotes" work inside single quotes' 16 | DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET="{ port: $MONGOLAB_PORT}" 17 | SINGLE_QUOTES_INSIDE_DOUBLE="single 'quotes' work inside double quotes" 18 | BACKTICKS_INSIDE_SINGLE='`backticks` work inside single quotes' 19 | BACKTICKS_INSIDE_DOUBLE="`backticks` work inside double quotes" 20 | BACKTICKS=`backticks` 21 | BACKTICKS_SPACED=` backticks ` 22 | DOUBLE_QUOTES_INSIDE_BACKTICKS=`double "quotes" work inside backticks` 23 | SINGLE_QUOTES_INSIDE_BACKTICKS=`single 'quotes' work inside backticks` 24 | DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS=`double "quotes" and single 'quotes' work inside backticks` 25 | EXPAND_NEWLINES="expand\nnew\nlines\r\b" 26 | DONT_EXPAND_UNQUOTED=dontexpand\nnewlines 27 | DONT_EXPAND_SQUOTED='dontexpand\nnewlines' 28 | DONT_EXPAND_SQUOTED='dontexpand\nnewlines' 29 | # COMMENTS=work 30 | INLINE_COMMENTS=inline comments # work #very #well 31 | INLINE_COMMENTS_SINGLE_QUOTES='inline comments outside of #singlequotes' # work 32 | INLINE_COMMENTS_DOUBLE_QUOTES="inline comments outside of #doublequotes" # work 33 | INLINE_COMMENTS_BACKTICKS=`inline comments outside of #backticks` # work 34 | INLINE_COMMENTS_SPACE=inline comments start with a#number sign. no space required. 35 | EQUAL_SIGNS=equals== 36 | RETAIN_INNER_QUOTES={"foo": "bar"} 37 | RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}' 38 | RETAIN_INNER_QUOTES_AS_BACKTICKS=`{"foo": "bar's"}` 39 | TRIM_SPACE_FROM_UNQUOTED= some spaced out string 40 | USERNAME=therealnerdybeast@example.tld 41 | SPACED_KEY = parsed 42 | export OPTION_A='postgres://localhost:5432/database?sslmode=disable' -------------------------------------------------------------------------------- /packages/core/__test__/env/mul.env: -------------------------------------------------------------------------------- 1 | BASIC=basic 2 | 3 | # previous line intentionally left blank 4 | AFTER_LINE=after_line 5 | EMPTY= 6 | SINGLE_QUOTES='single_quotes' 7 | SINGLE_QUOTES_SPACED=' single quotes ' 8 | DOUBLE_QUOTES="double_quotes" 9 | DOUBLE_QUOTES_SPACED=" double quotes " 10 | EXPAND_NEWLINES="expand\nnew\nlines" 11 | DONT_EXPAND_UNQUOTED=dontexpand\nnewlines 12 | DONT_EXPAND_SQUOTED='dontexpand\nnewlines' 13 | # COMMENTS=work 14 | EQUAL_SIGNS=equals== 15 | RETAIN_INNER_QUOTES={"foo": "bar"} 16 | 17 | RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}' 18 | TRIM_SPACE_FROM_UNQUOTED= some spaced out string 19 | USERNAME=therealnerdybeast@example.tld 20 | SPACED_KEY = parsed 21 | 22 | MULTI_DOUBLE_QUOTED="THIS 23 | IS 24 | A 25 | MULTILINE 26 | STRING" 27 | 28 | MULTI_SINGLE_QUOTED='THIS 29 | IS 30 | A 31 | MULTILINE 32 | STRING' 33 | 34 | MULTI_BACKTICKED=`THIS 35 | IS 36 | A 37 | "MULTILINE'S" 38 | STRING` -------------------------------------------------------------------------------- /packages/core/__test__/env/substitutions.env: -------------------------------------------------------------------------------- 1 | OPTION_A=1 2 | OPTION_B=${OPTION_A} 3 | OPTION_C=$OPTION_B 4 | OPTION_D=${OPTION_A}${OPTION_B} 5 | OPTION_E=${OPTION_NOT_DEFINED} 6 | -------------------------------------------------------------------------------- /packages/core/__test__/env/yml.env: -------------------------------------------------------------------------------- 1 | 2 | 3 | BASIC: basic 4 | 5 | # previous line intentionally left blank 6 | AFTER_LINE: after_line 7 | EMPTY: 8 | EMPTY_SINGLE_QUOTES: '' 9 | EMPTY_DOUBLE_QUOTES: "" 10 | EMPTY_BACKTICKS: `` 11 | SINGLE_QUOTES: 'single_quotes' 12 | SINGLE_QUOTES_SPACED: ' single quotes ' 13 | DOUBLE_QUOTES: "double_quotes" 14 | DOUBLE_QUOTES_SPACED: " double quotes " 15 | DOUBLE_QUOTES_INSIDE_SINGLE: 'double "quotes" work inside single quotes' 16 | DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET: "{ port: $MONGOLAB_PORT}" 17 | SINGLE_QUOTES_INSIDE_DOUBLE: "single 'quotes' work inside double quotes" 18 | BACKTICKS_INSIDE_SINGLE: '`backticks` work inside single quotes' 19 | BACKTICKS_INSIDE_DOUBLE: "`backticks` work inside double quotes" 20 | BACKTICKS: `backticks` 21 | BACKTICKS_SPACED: ` backticks ` 22 | DOUBLE_QUOTES_INSIDE_BACKTICKS: `double "quotes" work inside backticks` 23 | SINGLE_QUOTES_INSIDE_BACKTICKS: `single 'quotes' work inside backticks` 24 | DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS: `double "quotes" and single 'quotes' work inside backticks` 25 | EXPAND_NEWLINES: "expand\nnew\nlines\r\b" 26 | DONT_EXPAND_UNQUOTED: dontexpand\nnewlines 27 | DONT_EXPAND_SQUOTED: 'dontexpand\nnewlines' 28 | DONT_EXPAND_SQUOTED: 'dontexpand\nnewlines' 29 | # COMMENTS:work 30 | INLINE_COMMENTS: inline comments # work #very #well 31 | INLINE_COMMENTS_SINGLE_QUOTES: 'inline comments outside of #singlequotes' # work 32 | INLINE_COMMENTS_DOUBLE_QUOTES: "inline comments outside of #doublequotes" # work 33 | INLINE_COMMENTS_BACKTICKS: `inline comments outside of #backticks` # work 34 | INLINE_COMMENTS_SPACE: inline comments start with a#number sign. no space required. 35 | EQUAL_SIGNS: equals== 36 | RETAIN_INNER_QUOTES: {"foo": "bar"} 37 | RETAIN_INNER_QUOTES_AS_STRING: '{"foo": "bar"}' 38 | RETAIN_INNER_QUOTES_AS_BACKTICKS: `{"foo": "bar's"}` 39 | TRIM_SPACE_FROM_UNQUOTED: some spaced out string 40 | USERNAME: therealnerdybeast@example.tld 41 | SPACED_KEY: parsed 42 | export OPTION_A: 'postgres://localhost:5432/database?sslmode=disable' -------------------------------------------------------------------------------- /packages/core/__test__/exec.test.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import dotenv from "../"; 3 | 4 | describe("exec", () => { 5 | it("exec bash command", () => { 6 | const pathname = path.resolve(__dirname + "/env/.env"); 7 | const out = dotenv.exec([pathname], "bash", ["-c", 'echo "$BASIC"']); 8 | expect(out).toBe("basic\n"); 9 | }); 10 | 11 | it("exec nodejs command", () => { 12 | const pathname = path.resolve(__dirname, "./jsfile/hello.js"); 13 | const out = dotenv.exec([], "node", [pathname]); 14 | expect(out).toEqual("hello world\n"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/core/__test__/jsfile/.env: -------------------------------------------------------------------------------- 1 | BASIC="basic" 2 | KEY="value" 3 | -------------------------------------------------------------------------------- /packages/core/__test__/jsfile/hello.js: -------------------------------------------------------------------------------- 1 | console.log("hello world"); 2 | -------------------------------------------------------------------------------- /packages/core/__test__/load.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "../"; 2 | 3 | describe("load", () => { 4 | it("loadenv", () => { 5 | const path = __dirname + "/env/.env"; 6 | dotenv.load([path]); 7 | expect(process.env["BASIC"]).toEqual("basic"); 8 | }); 9 | 10 | it("overloadenv", () => { 11 | process.env["BASIC"] = "bas"; 12 | expect(process.env["BASIC"]).toEqual("bas"); 13 | const path = __dirname + "/env/.env"; 14 | dotenv.load([path]); 15 | expect(process.env["BASIC"]).toEqual("bas"); 16 | dotenv.load([path], { override: true }); 17 | expect(process.env["BASIC"]).toEqual("basic"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/core/__test__/marshal.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "../"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | describe("marshal", () => { 6 | it("marshal map", () => { 7 | const pathname = path.resolve(__dirname, "./env/.env"); 8 | const file = fs.readFileSync(pathname, "utf-8"); 9 | const map = dotenv.parse(file); 10 | const lines = dotenv.marshal(map); 11 | const newMap = dotenv.parse(lines); 12 | expect([...map]).toStrictEqual([...newMap]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/core/__test__/parse.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "../"; 2 | import fs from "fs"; 3 | 4 | describe("parse", () => { 5 | it("parse normal env", () => { 6 | const file = fs.readFileSync(__dirname + "/env/.env", "utf-8"); 7 | const map = dotenv.parse(file); 8 | const arr = [...map]; 9 | expect(arr).toStrictEqual([ 10 | ["BASIC", "basic"], 11 | ["AFTER_LINE", "after_line"], 12 | ["EMPTY", ""], 13 | ["EMPTY_SINGLE_QUOTES", ""], 14 | ["EMPTY_DOUBLE_QUOTES", ""], 15 | ["EMPTY_BACKTICKS", "``"], 16 | ["SINGLE_QUOTES", "single_quotes"], 17 | ["SINGLE_QUOTES_SPACED", " single quotes "], 18 | ["DOUBLE_QUOTES", "double_quotes"], 19 | ["DOUBLE_QUOTES_SPACED", " double quotes "], 20 | [ 21 | "DOUBLE_QUOTES_INSIDE_SINGLE", 22 | 'double "quotes" work inside single quotes', 23 | ], 24 | ["DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET", "{ port: $MONGOLAB_PORT}"], 25 | [ 26 | "SINGLE_QUOTES_INSIDE_DOUBLE", 27 | "single 'quotes' work inside double quotes", 28 | ], 29 | ["BACKTICKS_INSIDE_SINGLE", "`backticks` work inside single quotes"], 30 | ["BACKTICKS_INSIDE_DOUBLE", "`backticks` work inside double quotes"], 31 | ["BACKTICKS", "`backticks`"], 32 | ["BACKTICKS_SPACED", "` backticks `"], 33 | [ 34 | "DOUBLE_QUOTES_INSIDE_BACKTICKS", 35 | '`double "quotes" work inside backticks`', 36 | ], 37 | [ 38 | "SINGLE_QUOTES_INSIDE_BACKTICKS", 39 | "`single 'quotes' work inside backticks`", 40 | ], 41 | [ 42 | "DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS", 43 | "`double \"quotes\" and single 'quotes' work inside backticks`", 44 | ], 45 | ["EXPAND_NEWLINES", "expand\nnew\nlines\rb"], 46 | ["DONT_EXPAND_UNQUOTED", "dontexpand\\nnewlines"], 47 | ["DONT_EXPAND_SQUOTED", "dontexpand\\nnewlines"], 48 | ["INLINE_COMMENTS", "inline comments"], 49 | [ 50 | "INLINE_COMMENTS_SINGLE_QUOTES", 51 | "inline comments outside of #singlequotes", 52 | ], 53 | [ 54 | "INLINE_COMMENTS_DOUBLE_QUOTES", 55 | "inline comments outside of #doublequotes", 56 | ], 57 | ["INLINE_COMMENTS_BACKTICKS", "`inline comments outside of"], 58 | ["INLINE_COMMENTS_SPACE", "inline comments start with a"], 59 | ["EQUAL_SIGNS", "equals=="], 60 | ["RETAIN_INNER_QUOTES", '{"foo": "bar"}'], 61 | ["RETAIN_INNER_QUOTES_AS_STRING", '{"foo": "bar"}'], 62 | ["RETAIN_INNER_QUOTES_AS_BACKTICKS", '`{"foo": "bar\'s"}`'], 63 | ["TRIM_SPACE_FROM_UNQUOTED", "some spaced out string"], 64 | ["USERNAME", "therealnerdybeast@example.tld"], 65 | ["SPACED_KEY", "parsed"], 66 | ["OPTION_A", "postgres://localhost:5432/database?sslmode=disable"], 67 | ]); 68 | }); 69 | 70 | it("parse yaml env", () => { 71 | const file = fs.readFileSync(__dirname + "/env/yml.env", "utf-8"); 72 | const map = dotenv.parse(file); 73 | const arr = [...map]; 74 | expect(arr).toStrictEqual([ 75 | ["BASIC", "basic"], 76 | ["AFTER_LINE", "after_line"], 77 | ["EMPTY", ""], 78 | ["EMPTY_SINGLE_QUOTES", ""], 79 | ["EMPTY_DOUBLE_QUOTES", ""], 80 | ["EMPTY_BACKTICKS", "``"], 81 | ["SINGLE_QUOTES", "single_quotes"], 82 | ["SINGLE_QUOTES_SPACED", " single quotes "], 83 | ["DOUBLE_QUOTES", "double_quotes"], 84 | ["DOUBLE_QUOTES_SPACED", " double quotes "], 85 | [ 86 | "DOUBLE_QUOTES_INSIDE_SINGLE", 87 | 'double "quotes" work inside single quotes', 88 | ], 89 | ["DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET", "{ port: $MONGOLAB_PORT}"], 90 | [ 91 | "SINGLE_QUOTES_INSIDE_DOUBLE", 92 | "single 'quotes' work inside double quotes", 93 | ], 94 | ["BACKTICKS_INSIDE_SINGLE", "`backticks` work inside single quotes"], 95 | ["BACKTICKS_INSIDE_DOUBLE", "`backticks` work inside double quotes"], 96 | ["BACKTICKS", "`backticks`"], 97 | ["BACKTICKS_SPACED", "` backticks `"], 98 | [ 99 | "DOUBLE_QUOTES_INSIDE_BACKTICKS", 100 | '`double "quotes" work inside backticks`', 101 | ], 102 | [ 103 | "SINGLE_QUOTES_INSIDE_BACKTICKS", 104 | "`single 'quotes' work inside backticks`", 105 | ], 106 | [ 107 | "DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS", 108 | "`double \"quotes\" and single 'quotes' work inside backticks`", 109 | ], 110 | ["EXPAND_NEWLINES", "expand\nnew\nlines\rb"], 111 | ["DONT_EXPAND_UNQUOTED", "dontexpand\\nnewlines"], 112 | ["DONT_EXPAND_SQUOTED", "dontexpand\\nnewlines"], 113 | ["INLINE_COMMENTS", "inline comments"], 114 | [ 115 | "INLINE_COMMENTS_SINGLE_QUOTES", 116 | "inline comments outside of #singlequotes", 117 | ], 118 | [ 119 | "INLINE_COMMENTS_DOUBLE_QUOTES", 120 | "inline comments outside of #doublequotes", 121 | ], 122 | ["INLINE_COMMENTS_BACKTICKS", "`inline comments outside of"], 123 | ["INLINE_COMMENTS_SPACE", "inline comments start with a"], 124 | ["EQUAL_SIGNS", "equals=="], 125 | ["RETAIN_INNER_QUOTES", '{"foo": "bar"}'], 126 | ["RETAIN_INNER_QUOTES_AS_STRING", '{"foo": "bar"}'], 127 | ["RETAIN_INNER_QUOTES_AS_BACKTICKS", '`{"foo": "bar\'s"}`'], 128 | ["TRIM_SPACE_FROM_UNQUOTED", "some spaced out string"], 129 | ["USERNAME", "therealnerdybeast@example.tld"], 130 | ["SPACED_KEY", "parsed"], 131 | ["OPTION_A", "postgres://localhost:5432/database?sslmode=disable"], 132 | ]); 133 | }); 134 | 135 | it("parse substitutions env", () => { 136 | const file = fs.readFileSync(__dirname + "/env/substitutions.env", "utf-8"); 137 | const map = dotenv.parse(file); 138 | const arr = [...map]; 139 | expect(arr).toStrictEqual([ 140 | ["OPTION_A", "1"], 141 | ["OPTION_B", "1"], 142 | ["OPTION_C", "1"], 143 | ["OPTION_D", "11"], 144 | ["OPTION_E", ""], 145 | ]); 146 | }); 147 | 148 | it("parse mul env", () => { 149 | const file = fs.readFileSync(__dirname + "/env/mul.env", "utf-8"); 150 | const map = dotenv.parse(file); 151 | const arr = [...map]; 152 | expect(arr).toStrictEqual([ 153 | ["BASIC", "basic"], 154 | ["AFTER_LINE", "after_line"], 155 | ["EMPTY", ""], 156 | ["SINGLE_QUOTES", "single_quotes"], 157 | ["SINGLE_QUOTES_SPACED", " single quotes "], 158 | ["DOUBLE_QUOTES", "double_quotes"], 159 | ["DOUBLE_QUOTES_SPACED", " double quotes "], 160 | ["EXPAND_NEWLINES", "expand\nnew\nlines"], 161 | ["DONT_EXPAND_UNQUOTED", "dontexpand\\nnewlines"], 162 | ["DONT_EXPAND_SQUOTED", "dontexpand\\nnewlines"], 163 | ["EQUAL_SIGNS", "equals=="], 164 | ["RETAIN_INNER_QUOTES", '{"foo": "bar"}'], 165 | ["RETAIN_INNER_QUOTES_AS_STRING", '{"foo": "bar"}'], 166 | ["TRIM_SPACE_FROM_UNQUOTED", "some spaced out string"], 167 | ["USERNAME", "therealnerdybeast@example.tld"], 168 | ["SPACED_KEY", "parsed"], 169 | ["MULTI_DOUBLE_QUOTED", '"THIS\nIS\nA\nMULTILINE\nSTRING"'], 170 | ["MULTI_SINGLE_QUOTED", "'THIS\nIS\nA\nMULTILINE\nSTRING'"], 171 | ["MULTI_BACKTICKED", "`THIS"], 172 | ]); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /packages/core/__test__/write.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "../"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | describe("write", () => { 6 | it("write map to file", () => { 7 | const map = new Map(); 8 | map.set("BASIC", "basic"); 9 | map.set("KEY", "value"); 10 | const filepath = path.resolve(__dirname, "./jsfile/.env"); 11 | dotenv.write(map, filepath); 12 | const newMap = dotenv.parse(fs.readFileSync(filepath, "utf-8")); 13 | expect([...map]).toStrictEqual([...newMap]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/core/common.ts: -------------------------------------------------------------------------------- 1 | export function trimPrefix(s: string, prefix: string) { 2 | if (s.startsWith(prefix)) { 3 | return s.substring(prefix.length); 4 | } 5 | return s; 6 | } 7 | 8 | const doubleQuoteSpecialChars = '\\\n\r"'; 9 | export function doubleQuoteEscape(line: string): string { 10 | for (const char of doubleQuoteSpecialChars) { 11 | let toReplace = "\\" + char; 12 | if (char == "\n") { 13 | toReplace = "\\n"; 14 | } 15 | if (char == "\r") { 16 | toReplace = "\\r"; 17 | } 18 | line = line.replaceAll(char, toReplace); 19 | } 20 | return line; 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { Options } from "./types"; 3 | import Parser from "./parser"; 4 | import { doubleQuoteEscape } from "./common"; 5 | const spawn = require("cross-spawn"); 6 | 7 | class DotEnv { 8 | private parser = new Parser(); 9 | public load( 10 | filenames: string[], 11 | options: Options = { 12 | override: false, 13 | } 14 | ): void { 15 | const { override } = options; 16 | for (const filename of filenames) { 17 | this.loadFile(filename, override); 18 | } 19 | } 20 | public parse(file: string): Map { 21 | return this.parser.parse(file); 22 | } 23 | public exec(filenames: string[], cmd: string, cmdArgs: string[]) { 24 | this.load(filenames, { override: true }); 25 | const ls = spawn.sync(cmd, cmdArgs, { env: process.env }); 26 | return ls.stdout.toString("utf-8"); 27 | } 28 | public write(map: Map, filename: string): boolean { 29 | const lines = this.marshal(map).concat("\n"); 30 | try { 31 | fs.writeFileSync(filename, lines); 32 | return true; 33 | } catch (error) { 34 | console.error(error); 35 | return false; 36 | } 37 | } 38 | public marshal(map: Map): string { 39 | const lines: string[] = []; 40 | for (const [key, value] of map) { 41 | if (!isNaN(parseInt(value))) { 42 | lines.push(`${key}=${value}`); 43 | } else { 44 | lines.push(`${key}="${doubleQuoteEscape(value)}"`); 45 | } 46 | } 47 | return lines.join("\n"); 48 | } 49 | 50 | private loadFile(filename: string, overload: boolean): void { 51 | const map = this.readFile(filename); 52 | for (const [key, value] of map) { 53 | if (!Object.prototype.hasOwnProperty.call(process.env, key)) { 54 | process.env[key] = value; 55 | } else { 56 | if (overload) { 57 | process.env[key] = value; 58 | } 59 | } 60 | } 61 | } 62 | private readFile(filename: string): Map { 63 | try { 64 | const file = fs.readFileSync(filename, "utf-8"); 65 | return this.parse(file); 66 | } catch (error) { 67 | throw new Error(error as string); 68 | } 69 | } 70 | } 71 | 72 | export default new DotEnv(); 73 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsdotenv/core", 3 | "version": "2.0.9", 4 | "description": "Loads environment variables from .env file", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "bugs": { 9 | "url": "https://github.com/childrentime/dotenv/issues" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/childrentime/dotenv", 14 | "directory": "packages/core" 15 | }, 16 | "sideEffects": false, 17 | "main": "dist/index.js", 18 | "typings": "dist/index.d.ts", 19 | "module": "dist/esm/index.js", 20 | "license": "MIT", 21 | "engines": { 22 | "node": ">=14" 23 | }, 24 | "files": [ 25 | "dist/", 26 | "CHANGELOG.md", 27 | "LICENSE.md", 28 | "README.md" 29 | ], 30 | "keywords": [ 31 | "dotenv", 32 | "env", 33 | ".env", 34 | "environment", 35 | "variables", 36 | "config", 37 | "settings" 38 | ], 39 | "dependencies": { 40 | "cross-spawn": "^7.0.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/parser.ts: -------------------------------------------------------------------------------- 1 | import { trimPrefix } from "./common"; 2 | 3 | export default class Parser { 4 | private map = new Map(); 5 | 6 | public parse(file: string): Map { 7 | this.map = new Map(); 8 | const LINE = 9 | /(?:^|\A)\s*(?:export\s+)?([\w.]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|[^\#\r\n]+)?\s*(?:\#.*)?(?:$|\z)/gm; 10 | 11 | const lines = file.replace(/\r\n?/gm, "\n"); 12 | // Process matches 13 | let match; 14 | while ((match = LINE.exec(lines))) { 15 | const key = match[1]; 16 | // Default undefined or null to empty string 17 | const valueString = match[2] || ""; 18 | const value = this.parseValue(valueString); 19 | this.map.set(key, value); 20 | } 21 | return this.map; 22 | } 23 | 24 | private parseValue(value: string): string { 25 | const singleQuotesRegex = /^'(.*)'$/; 26 | const doubleQuotesRegex = /^"(.*)"$/; 27 | const escapeRegex = /\\./g; 28 | const unescapeCharsRegex = /\\([^$])/g; 29 | 30 | value = value.trim(); 31 | 32 | // check if we've got quoted values or possible escapes 33 | if (value.length > 1) { 34 | const singleQoutes = value.match(singleQuotesRegex); 35 | const doubleQoutes = value.match(doubleQuotesRegex); 36 | 37 | if (singleQoutes != null || doubleQoutes != null) { 38 | // pull the quotes off the edges 39 | value = value.substring(1, value.length - 1); 40 | } 41 | 42 | // doubelQouts should expand 43 | if (doubleQoutes != null) { 44 | // expand newlines 45 | value = value.replaceAll(escapeRegex, (match: string) => { 46 | const c = trimPrefix(match, "\\"); 47 | switch (c) { 48 | case "n": 49 | return "\n"; 50 | case "r": 51 | return "\r"; 52 | default: 53 | return match; 54 | } 55 | }); 56 | // unescape characters 57 | value = value.replaceAll(unescapeCharsRegex, "$1"); 58 | } 59 | 60 | if (singleQoutes === null) { 61 | value = this.expandVariables(value); 62 | } 63 | } 64 | 65 | return value; 66 | } 67 | private expandVariables(v: string): string { 68 | const expandVarRegex = /(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?/gy; 69 | return v.replaceAll(expandVarRegex, (match: string) => { 70 | const submatch = match.split(expandVarRegex); 71 | if (submatch === null) { 72 | return match; 73 | } 74 | if (submatch[1] == "\\" || submatch[2] == "(") { 75 | return submatch[0].substring(1, submatch[0].length); 76 | } else if (submatch[4] != "") { 77 | return this.map.get(submatch[4]) || ""; 78 | } 79 | return match; 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const babel = require("@rollup/plugin-babel").default; 3 | const nodeResolve = require("@rollup/plugin-node-resolve").default; 4 | const copy = require("rollup-plugin-copy"); 5 | const commonjs = require("@rollup/plugin-commonjs"); 6 | 7 | const { name: packageName } = require("./package.json"); 8 | const { getOutputDir } = require("../../rollup.utils"); 9 | 10 | module.exports = function rollup() { 11 | let sourceDir = "packages/core"; 12 | let outputDir = getOutputDir(packageName); 13 | let outputDist = path.join(outputDir, "dist"); 14 | 15 | return [ 16 | { 17 | input: `${sourceDir}/index.ts`, 18 | output: { 19 | dir: outputDist, 20 | format: "cjs", 21 | preserveModules: true, 22 | exports: "named", 23 | }, 24 | plugins: [ 25 | babel({ 26 | babelHelpers: "bundled", 27 | exclude: /node_modules/, 28 | extensions: [".ts"], 29 | }), 30 | nodeResolve({ extensions: [".ts"] }), 31 | commonjs(), 32 | copy({ 33 | targets: [ 34 | { src: `LICENSE.md`, dest: [outputDir, sourceDir] }, 35 | { src: `${sourceDir}/package.json`, dest: [outputDir] }, 36 | { src: `${sourceDir}/README.md`, dest: outputDir }, 37 | { src: `${sourceDir}/CHANGELOG.md`, dest: outputDir }, 38 | ], 39 | }), 40 | ], 41 | }, 42 | { 43 | input: `${sourceDir}/index.ts`, 44 | output: { 45 | format: "esm", 46 | dir: path.join(outputDist, "esm"), 47 | }, 48 | plugins: [ 49 | babel({ 50 | babelHelpers: "bundled", 51 | exclude: /node_modules/, 52 | extensions: [".ts"], 53 | }), 54 | nodeResolve({ extensions: [".ts"] }), 55 | commonjs(), 56 | ], 57 | }, 58 | ]; 59 | }; 60 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts", "rollup.config.js"], 3 | "exclude": ["dist", "__test__", "node_modules"], 4 | "compilerOptions": { 5 | "lib": ["es2021"], 6 | "module": "CommonJS", 7 | "target": "es2021", 8 | 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "moduleResolution": "node", 14 | "declaration": true, 15 | "emitDeclarationOnly": true, 16 | "rootDir": ".", 17 | "outDir": "../../build/node_modules/@jsdotenv/core/dist" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/types.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | override: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/utils.ts: -------------------------------------------------------------------------------- 1 | export function isIgnoredLine(line: string): boolean { 2 | const trimmedline = line.trim(); 3 | return trimmedline.length === 0 || trimmedline.startsWith("#"); 4 | } 5 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const core = require("./packages/core/rollup.config"); 2 | const cli = require("./packages/cli/rollup.config"); 3 | 4 | module.exports = function rollup() { 5 | return [...core(), ...cli()]; 6 | }; 7 | -------------------------------------------------------------------------------- /rollup.utils.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const outputDir = "build"; 4 | 5 | function getOutputDir(packageName) { 6 | return path.join(outputDir, "node_modules", packageName); 7 | } 8 | 9 | module.exports = { 10 | getOutputDir, 11 | }; 12 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from "cross-spawn"; 2 | import { deleteAsync } from "del"; 3 | 4 | const exec = (command: string, args: string[]) => { 5 | const handleData = (data: any) => console.log(data.toString().trim()); 6 | let handleError = (data: Error) => console.error(data.toString().trim()); 7 | 8 | return new Promise((resolve, reject) => { 9 | const ls = spawn(command, args, { cwd: process.cwd() }); 10 | ls.stdout.on("data", handleData); 11 | ls.stderr.on("data", handleData); 12 | ls.on("error", handleError); 13 | ls.on("close", (code) => { 14 | if (code === 0) { 15 | resolve(void 0); 16 | } else { 17 | reject(new Error(`${command} exited with code ${code}`)); 18 | } 19 | }); 20 | }); 21 | }; 22 | 23 | deleteAsync("./build"); 24 | exec("yarn", ["rollup", "--config", "rollup.config.js"]) 25 | .then(() => exec("yarn", ["tsc", "--build"])) 26 | .then(() => process.exit(0)) 27 | .catch((err) => { 28 | console.error(err); 29 | process.exit(1); 30 | }); 31 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { execSync } from "child_process"; 3 | 4 | const buildDir = path.resolve(__dirname, "../build/node_modules"); 5 | const createDir = path.resolve( 6 | __dirname, 7 | "../build/node_modules/create-dotenv" 8 | ); 9 | 10 | const publish = (dir: string, tag: string) => { 11 | execSync(`npm publish --access public --tag ${tag} ${dir}`, { 12 | stdio: "inherit", 13 | }); 14 | }; 15 | 16 | const run = async () => { 17 | const tag = "latest"; 18 | for (const name of ["core"]) { 19 | publish(path.join(buildDir, "@jsdotenv", name), tag); 20 | } 21 | publish(createDir, tag); 22 | }; 23 | 24 | run().then( 25 | () => { 26 | process.exit(0); 27 | }, 28 | (error) => { 29 | console.error(error); 30 | process.exit(1); 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "strict": true, 5 | "esModuleInterop": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import jsonfile from "jsonfile"; 3 | 4 | const rootDir = path.resolve(__dirname, ".."); 5 | function packageJson(packageName: string, directory = "") { 6 | return path.join(rootDir, directory, packageName, "package.json"); 7 | } 8 | 9 | export async function getPackageVersion(packageName: string) { 10 | let file = packageJson(packageName, "packages"); 11 | let json = await jsonfile.readFile(file); 12 | return json.version; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/version.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import { getPackageVersion } from "./utils"; 3 | 4 | const run = async () => { 5 | const currentVersion = await getPackageVersion("core"); 6 | execSync(`git commit --all --message="Version ${currentVersion}"`); 7 | execSync(`git tag -a -m "Version ${currentVersion}" v${currentVersion}`); 8 | }; 9 | 10 | run().then( 11 | () => { 12 | process.exit(0); 13 | }, 14 | (error) => { 15 | console.error(error); 16 | process.exit(1); 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "exclude": ["node_modules", "build"], 4 | "references": [ 5 | { 6 | "path": "packages/core" 7 | } 8 | ] 9 | } 10 | --------------------------------------------------------------------------------