├── .nvmrc ├── .gitignore ├── test ├── no_files │ └── .gitkeep ├── config_array │ ├── two.json │ ├── one.js │ └── package.json ├── config_path │ ├── foo.js │ └── package.json ├── .eslintrc ├── invalid │ └── config.default.js └── enchilada │ ├── package.json │ ├── config.json │ ├── config.js │ ├── config.local.js │ ├── config.local.json │ ├── config.default.json │ └── config.default.js ├── .eslintignore ├── host-scripts └── docker-run.sh ├── CHANGELOG.md ├── license.txt ├── package-overrides-tap.js ├── .eslintrc.js ├── package.json ├── default-locations-tap.js ├── config3.js └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 11.6.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node 2 | -------------------------------------------------------------------------------- /test/no_files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/config_array/two.json: -------------------------------------------------------------------------------- 1 | {"one": 1.1} 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/invalid/config.default.js 2 | -------------------------------------------------------------------------------- /test/config_path/foo.js: -------------------------------------------------------------------------------- 1 | exports.foo = 'Forty-Two' 2 | -------------------------------------------------------------------------------- /test/config_array/one.js: -------------------------------------------------------------------------------- 1 | exports.one = 1 2 | exports.two = 'two' 3 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/config_path/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "config3Paths": "./foo" 3 | } 4 | -------------------------------------------------------------------------------- /test/invalid/config.default.js: -------------------------------------------------------------------------------- 1 | Oops this is not actually a javascript file 2 | -------------------------------------------------------------------------------- /test/config_array/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "config3Paths": ["./one", "two"] 3 | } 4 | -------------------------------------------------------------------------------- /test/enchilada/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-package-for-config3" 3 | } 4 | -------------------------------------------------------------------------------- /test/enchilada/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fromConfigDefaultJs": "from config.json", 3 | "fromConfigDefaultJson": "from config.json", 4 | "fromConfigJs": "from config.json", 5 | "fromConfigJson": "from config.json", 6 | "fromConfigLocalJs": "from config.json", 7 | "fromConfigLocalJson": "from config.json" 8 | } 9 | -------------------------------------------------------------------------------- /test/enchilada/config.js: -------------------------------------------------------------------------------- 1 | exports.fromConfigDefaultJson = 'from config.js' 2 | exports.fromConfigDefaultJs = 'from config.js' 3 | exports.fromConfigJson = 'from config.js' 4 | exports.fromConfigJs = 'from config.js' 5 | exports.fromConfigLocalJson = 'from config.js' 6 | exports.fromConfigLocalJs = ['should', 'get', 'overridden'] 7 | -------------------------------------------------------------------------------- /test/enchilada/config.local.js: -------------------------------------------------------------------------------- 1 | exports.fromConfigDefaultJson = 'from config.local.js' 2 | exports.fromConfigDefaultJs = 'from config.local.js' 3 | exports.fromConfigJson = 'from config.local.js' 4 | exports.fromConfigJs = 'from config.local.js' 5 | exports.fromConfigLocalJson = 'from config.local.js' 6 | exports.fromConfigLocalJs = ['should', 'be', 'used'] 7 | -------------------------------------------------------------------------------- /test/enchilada/config.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "fromConfigDefaultJs": "from config.local.json", 3 | "fromConfigDefaultJson": "from config.local.json", 4 | "fromConfigJs": "from config.local.json", 5 | "fromConfigJson": "from config.local.json", 6 | "fromConfigLocalJs": "from config.local.json", 7 | "fromConfigLocalJson": "from config.local.json" 8 | } 9 | -------------------------------------------------------------------------------- /test/enchilada/config.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "fromConfigDefaultJs": "from config.default.json", 3 | "fromConfigDefaultJson": "from config.default.json", 4 | "fromConfigJs": "from config.default.json", 5 | "fromConfigJson": "from config.default.json", 6 | "fromConfigLocalJs": "from config.default.json", 7 | "fromConfigLocalJson": "from config.default.json" 8 | } 9 | -------------------------------------------------------------------------------- /test/enchilada/config.default.js: -------------------------------------------------------------------------------- 1 | exports.fromConfigDefaultJson = 'from config.default.js' 2 | exports.fromConfigDefaultJs = 'from config.default.js' 3 | exports.fromConfigJson = 'from config.default.js' 4 | exports.fromConfigJs = 'from config.default.js' 5 | exports.fromConfigLocalJson = 'from config.default.js' 6 | exports.fromConfigLocalJs = ['should', 'get', 'overridden'] 7 | -------------------------------------------------------------------------------- /host-scripts/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Please Use Google Shell Style: https://google.github.io/styleguide/shell.xml 4 | 5 | # ---- Start unofficial bash strict mode boilerplate 6 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/ 7 | set -o errexit # always exit on error 8 | set -o errtrace # trap errors in functions as well 9 | set -o pipefail # don't ignore exit codes when piping output 10 | set -o posix # more strict failures in subshells 11 | # set -x # enable debugging 12 | 13 | IFS="$(printf "\n\t")" 14 | # ---- End unofficial bash strict mode boilerplate 15 | 16 | cd "$(dirname "$0")/.." 17 | exec docker run --rm --interactive --tty \ 18 | --volume "${PWD}:/host" --workdir /host \ 19 | --env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/node_modules/.bin \ 20 | --user $(id -u) \ 21 | node:$(cat .nvmrc)-alpine sh 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | # [1.1.0](https://github.com/focusaurus/config3/compare/v1.0.4...v1.1.0) (2019-02-28) 6 | 7 | 8 | 9 | 10 | ## [1.0.4](https://github.com/focusaurus/config3/compare/v1.0.3...v1.0.4) (2018-05-08) 11 | 12 | 13 | 14 | 15 | ## [1.0.3](https://github.com/focusaurus/config3/compare/v1.0.2...v1.0.3) (2016-10-04) 16 | 17 | 18 | 19 | 20 | ## [1.0.2](https://github.com/focusaurus/config3/compare/v1.0.1...v1.0.2) (2016-10-04) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * no-op commit to bump version ([95510b7](https://github.com/focusaurus/config3/commit/95510b7)) 26 | 27 | 28 | 29 | 30 | ## [1.0.1](https://github.com/focusaurus/config3/compare/v1.0.0...v1.0.1) (2016-10-04) 31 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | Copyright (c) 2016 Peter Lyons LLC 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. -------------------------------------------------------------------------------- /package-overrides-tap.js: -------------------------------------------------------------------------------- 1 | process.env.CONFIG3_TEST = "true"; 2 | const path = require("path"); 3 | const tap = require("tap"); 4 | const config3 = require("./"); 5 | 6 | tap.test("a directory with package.json set with a path", test => { 7 | const config = config3(path.join(__dirname, "test", "config_path")); 8 | test.match(config, { foo: "Forty-Two" }); 9 | test.end(); 10 | }); 11 | 12 | tap.test("a directory with package.json set with a an array", test => { 13 | const config = config3(path.join(__dirname, "test", "config_array")); 14 | test.match(config, { one: 1.1, two: "two" }); 15 | test.end(); 16 | }); 17 | 18 | tap.test("a directory with all possible config files", test => { 19 | const config = config3(path.join(__dirname, "test", "enchilada")); 20 | test.same(config.fromConfigDefaultJson, "from config.local.js"); 21 | test.same(config.fromConfigDefaultJs, "from config.local.js"); 22 | test.same(config.fromConfigJson, "from config.local.js"); 23 | test.same(config.fromConfigJs, "from config.local.js"); 24 | test.same(config.fromConfigLocalJson, "from config.local.js"); 25 | test.match(config.fromConfigLocalJs, ["should", "be", "used"]); 26 | test.end(); 27 | }); 28 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 2017, 4 | sourceType: "script" 5 | }, 6 | extends: ["eslint:recommended", "airbnb-base", "prettier"], 7 | rules: { 8 | "block-scoped-var": "error", 9 | "consistent-this": ["error", "self"], 10 | "func-style": ["error", "declaration"], 11 | "no-param-reassign": ["error", {props: false}], 12 | "global-require": "off", 13 | "import/no-extraneous-dependencies": [ 14 | "error", 15 | {devDependencies: ["**/*test*", "bin/*", "**/*-tap.js"]} 16 | ], 17 | "lines-around-directive": "off", 18 | "max-len": [ 19 | "warn", 20 | { 21 | ignoreStrings: true, 22 | ignoreTemplateLiterals: true, 23 | ignoreTrailingComments: true, 24 | ignoreComments: true, 25 | ignoreRegExpLiterals: true 26 | } 27 | ], 28 | "max-nested-callbacks": ["error", 4], 29 | "no-else-return": "error", 30 | "no-global-require": "off", 31 | "no-nested-ternary": "error", 32 | "no-process-exit": 1, 33 | "no-sync": 1, 34 | "no-undefined": "error", 35 | "no-underscore-dangle": "off", 36 | "no-warning-comments": "warn", 37 | strict: ["off", "safe"] 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config3", 3 | "version": "1.1.0", 4 | "description": "Cascade config files with sensible default, local, deployed files", 5 | "main": "./config3.js", 6 | "scripts": { 7 | "test": "tap **/*-tap.js", 8 | "lint": "eslint .", 9 | "release": "standard-version" 10 | }, 11 | "bin": "./config3.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/focusaurus/config3.git" 15 | }, 16 | "keywords": [ 17 | "config", 18 | "configuration", 19 | "server" 20 | ], 21 | "files": [ 22 | "config3.js" 23 | ], 24 | "author": "Peter Lyons (http://peterlyons.com/)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/focusaurus/config3/issues" 28 | }, 29 | "homepage": "https://github.com/focusaurus/config3", 30 | "dependencies": { 31 | "config-extend": "0.1.1", 32 | "debug": "^4.1.1", 33 | "pathval": "1.1.0", 34 | "verror": "1.10.0" 35 | }, 36 | "devDependencies": { 37 | "eslint": "^5.14.1", 38 | "eslint-config-airbnb-base": "^13.1.0", 39 | "eslint-config-prettier": "^4.1.0", 40 | "eslint-plugin-import": "^2.16.0", 41 | "eslint-plugin-promise": "^4.0.1", 42 | "rmrf": "2.0.0", 43 | "standard-version": "^5.0.1", 44 | "tap": "^12.5.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /default-locations-tap.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | process.env.CONFIG3_TEST = "true"; 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const rmrf = require("rmrf"); 6 | const tap = require("tap"); 7 | const config3 = require("./"); 8 | /* eslint-disable no-sync */ 9 | const TEST_APP_ROOT = path.join(__dirname, "test", "test_app_root"); 10 | tap.beforeEach(done => { 11 | rmrf(TEST_APP_ROOT); 12 | fs.mkdirSync(TEST_APP_ROOT); 13 | done(); 14 | }); 15 | 16 | tap.afterEach(done => { 17 | rmrf(TEST_APP_ROOT); 18 | done(); 19 | }); 20 | 21 | tap.test("a directory with no package.json and no config files", test => { 22 | const config = config3(path.join(__dirname, "test", "no_files")); 23 | test.same(Object.keys(config).length, 0); 24 | test.end(); 25 | }); 26 | 27 | tap.test("a file that exists but is invalid", test => { 28 | const testRoot = path.join(__dirname, "test", "invalid"); 29 | try { 30 | config3(testRoot); 31 | test.fail("invalid config must throw an error"); 32 | } catch (error) { 33 | tap.ok(error); 34 | tap.match(error, { path: `${testRoot}/config.default` }); 35 | } 36 | test.end(); 37 | }); 38 | 39 | ["config.default.js", "config.js", "config.local.js"].forEach(jsPath => { 40 | tap.test(`a directory with just ${jsPath}`, test => { 41 | fs.writeFileSync( 42 | path.join(TEST_APP_ROOT, jsPath), 43 | "exports.foo = 42;\n", 44 | "utf8" 45 | ); 46 | const config = config3(TEST_APP_ROOT); 47 | tap.match(config, { foo: 42 }); 48 | test.end(); 49 | }); 50 | }); 51 | 52 | ["config.default.json", "config.json", "config.local.json"].forEach(jsPath => { 53 | tap.test(`a directory with just ${ jsPath}`, test => { 54 | fs.writeFileSync( 55 | path.join(TEST_APP_ROOT, jsPath), 56 | JSON.stringify({ 57 | foo: 42 58 | }), 59 | "utf8" 60 | ); 61 | const config = config3(TEST_APP_ROOT); 62 | test.match(config, { foo: 42 }); 63 | test.end(); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /config3.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const configExtend = require("config-extend"); 3 | const debug = require("debug")("config3"); 4 | const path = require("path"); 5 | const VError = require("verror"); 6 | 7 | function load(appRoot, configPathArg) { 8 | const configPath = path.resolve(appRoot, configPathArg); 9 | const quotedPath = `'${configPath}'`; 10 | try { 11 | debug(`Loading ${quotedPath}`); 12 | // eslint-disable-next-line import/no-dynamic-require 13 | const config = require(configPath); 14 | debug(`Loaded ${quotedPath}`); 15 | return config; 16 | } catch (error) { 17 | debug(`Did not load ${quotedPath}`); 18 | if (error.name === "SyntaxError") { 19 | const verror = new VError(error, `Invalid config file: ${quotedPath}`); 20 | verror.path = configPath; 21 | throw verror; 22 | } 23 | return null; 24 | } 25 | } 26 | 27 | function main(appRootArg) { 28 | const appRoot = appRootArg || path.resolve(path.join(__dirname, "../..")); 29 | debug(`appRoot is '${appRoot}'`); 30 | const loadRoot = load.bind(null, appRoot); 31 | const packageJson = loadRoot("package.json") || {}; 32 | 33 | function loadPaths(paths) { 34 | const configs = paths.map(loadRoot); 35 | return configExtend(...configs); 36 | } 37 | 38 | // If your package.json has "config3Paths": ["./myConfig"] 39 | // then we load only the files listed there 40 | if (Array.isArray(packageJson.config3Paths)) { 41 | return loadPaths(packageJson.config3Paths); 42 | } 43 | 44 | // If your package.json has "config3Paths": "./myConfig" 45 | // then we just load that single path 46 | if (typeof packageJson.config3Paths === "string") { 47 | return loadRoot(packageJson.config3Paths); 48 | } 49 | 50 | // Default and recommended fallback paths for fans of simplicity 51 | // read config.default, config, config.local, /etc/packagename/config 52 | const defaultPaths = ["config.default", "config", "config.local"]; 53 | if (typeof packageJson.name === "string") { 54 | defaultPaths.push(path.join("/etc", packageJson.name, "config")); 55 | } 56 | return loadPaths(defaultPaths); 57 | } 58 | 59 | if (process.env.CONFIG3_TEST) { 60 | module.exports = main; 61 | } else { 62 | module.exports = main(); 63 | } 64 | 65 | const cliPath = process.argv[2]; 66 | if (require.main === module && cliPath) { 67 | const pathval = require("pathval"); 68 | const value = pathval.get(module.exports, cliPath); 69 | if (typeof value !== "undefined") { 70 | // eslint-disable-next-line no-console 71 | console.log(value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # config3 - Get Your Settings, No Bananas 2 | 3 | ## How to Use It 4 | 5 | - Define your default configuration in `/config.default.js` 6 | - This should be a CommonJS module that exports an object which is your application's config values. 7 | - These should be sensible defaults for local development. 8 | - You may also use `config.default.json`, which should be a JSON file. 9 | - You may also use `config.js` or `config.json` 10 | - If necessary, define local or developer-specific overrides in `/config.local.js` 11 | - These will take precedence over the defaults 12 | - Configurations are merged, so you only need to specify the particular values you want to override, not an entirely new configuration. 13 | - `config.local.json` or `config.local.js` will both work 14 | - Include these file paths in your `.gitignore` as these are by definition specific to a particular developer or environment and should not be tracked in source control. 15 | - For deployment, put your configuration overrides in `/etc//config.js` 16 | - `` is your npm package name from your project's `package.json` file 17 | - `/etc//config.json` will also work 18 | - Don't store these in git. This is where your secrets like API keys, database credentials, etc go 19 | - In any code that needs configuration, just load it 20 | - `var config = require("config3");` 21 | - Your settings will be there. 22 | - Things get merged and overridden as you would expect. Arrays get replaced. The [config-extend](https://www.npmjs.org/package/config-extend) module handles this logic for us. 23 | - config files are loaded with regular old `require` which looks for `.js` first and falls back to `.json` otherwise. 24 | - Thus the full resolution order, first to last, with last entry winning is: 25 | - `config.default.js` OR `config.default.json` (NOT both) 26 | - `config.js` OR `config.json` (NOT both) 27 | - `config.local.js` OR `config.local.json` (NOT both) 28 | - `/etc//config.js` OR `/etc//config.json` (NOT both) 29 | 30 | ## Example 31 | 32 | **/config.json** 33 | 34 | {"port": 3000, "dbUrl": "mongodb://localhost/myapp", "fbAppId": "12345"} 35 | 36 | **/config.local.json** 37 | 38 | {"port": 4500} 39 | 40 | **/etc/myapp/config.json** 41 | 42 | {"dbUrl": "mongodb://192.168.1.17/myapp-production", "fbAppId": "REAL_FB_APP_ID"} 43 | 44 | ## Getting values with the CLI 45 | 46 | This module comes with a command line program also called `config3` that takes a property path as the only argument and prints out the corresponding value from your application's configuration. This supports property path notation al la `db.connection.poolSize` via the `pathval` npm package. 47 | 48 | This comes in handy for automating stuff during builds and deployments. 49 | 50 | ## CLI Example 51 | 52 | `./node_modules/.bin/config3 'emails.admins[0]'` 53 | 54 | Prints out "one@example.com" given a config of `{emails: {admins: ["one@example.com"]}}` 55 | 56 | 57 | #Motivation and Philosophy 58 | 59 | There are many similar modules already written and published to the npm registry. Why yet another? I find problems with most of the existing ones as follows: 60 | 61 | - poisoned by the Ruby on Rails notion of `RAILS_ENV=production` (NODE_ENV for us) 62 | - This whole system is flawed and an antipattern 63 | - The primary way this screws you is as follows: 64 | - some module you depend on alters its behavior based on `NODE_ENV`. Typically this might be something like enabling a cache in `production` but disabling it otherwise. 65 | - Case in point: [express.js will cache views in production only](https://github.com/visionmedia/express/blob/0719e5f402ff4b8129f19fe3d0704b31733f1190/lib/application.js#L76) 66 | - So you set `NODE_ENV=staging` on your staging system and use one of the npm config packages that loads a `staging.yaml` file. Now your staging server is way out of alignment with production. 67 | - So I think this entire notion is fundamentally the wrong way to think about configuration code that looks at `NODE_ENV` should be removed in favor of explicit options. Packages in npm should assume production-type configuration by default and should allow appropriate changes for development when passed explicit granular options to do development things like enabling source maps, disabling caches, printing debug output, etc. 68 | - Uses YAML configuration files. 69 | - YAML is just goofy and rubyish and we shouldn't be bringing it along into the node.js ecosystem. It's been involved in many of ruby's security issues. Just keep your configuration simple. If you need 30 key/value pairs or complicated data structures, paragraphs of comments and logic in your configuration, you are probably building a monolithic monster app that should be split into smaller services. 70 | - But what about [12 Factor Apps](http://12factor.net/)? 71 | - While mostly I think the 12 factors are quite correct and excellent, when it comes to environment variables, I disagree. Flat files on the filesystem are better as per my blog post [Environment Variables Considered Harmful](http://peterlyons.com/problog/2010/02/environment-variables-considered-harmful) 72 | - You should have to read 2 or at most 3 files to figure out which values your running application has loaded, which is why I called this module `config3` 73 | 74 | ## Debugging 75 | 76 | config3 uses the [debug](https://github.com/visionmedia/debug) package by TJ Holowaychuk. Normally, no debug information is output. To have debug statements written to stdout, set the DEBUG environment variable to `config3` or a colon-delimited string containing config3 like `express:config3:socket.io`. 77 | 78 | `DEBUG=config3 node myapp.js` 79 | 80 | 81 | ## Notes on Existing Modules 82 | - [config](https://www.npmjs.org/package/config): NODE_ENV, yaml 83 | - [global-config](https://www.npmjs.org/package/global-config): name says it all. Creates global variables. Fail. 84 | - [config-env](https://www.npmjs.org/package/config-env) Do we really need a JS API to define some key/value pairs? 85 | - [env-config](https://www.npmjs.org/package/env-config) Not a bad choice if you like the 12-Factor environment variable thing, but I personally do not. Also, written in CoffeeScript. 86 | - [json-config](https://www.npmjs.org/package/json-config) No way to have defaults and overrides. Author doesn't know `require` can load JSON files directly. Throws exceptions. 87 | - [yaml-config](https://www.npmjs.org/package/yaml-config) YAML. NODE_ENV. 88 | - [feather-config](https://www.npmjs.org/package/feather-config) If you think how your config gets built should be a supremely flexible puzzle that you can solve at 2am when production is down, this one actually looks pretty solid to me. If I move away from config3, feather-config would probably be my next choice. It's more complicated than we really want/need so I'd rather have a small number of specific, unchangeable file paths to look at not have configuration values possibly come from as many different places as possible. 89 | - [mods-config](https://www.npmjs.org/package/mods-config) No docs. Uses `process.cwd()`. 90 | - [dh-config](https://www.npmjs.org/package/dh-config) `NODE_ENV` 91 | - [lib-config](https://www.npmjs.org/package/lib-config) This is just a library, not a full config system. 92 | - [config-node](https://www.npmjs.org/package/config-node) No docs. Repository URL incorrect. 93 | - [nxt-config](https://www.npmjs.org/package/nxt-config) ini file syntax. Windows. Bitbucket. 2 years stale. 94 | - [config-reader](https://www.npmjs.org/package/config-reader) custom, complex config file format 95 | - [local-config](https://www.npmjs.org/package/local-config) Mostly simple and sane. No override capability. 96 | - [context-config](https://www.npmjs.org/package/context-config) Addressing a much more complex problem. 97 | - [pwf-config](https://www.npmjs.org/package/pwf-config) Not sure what this is. 98 | - [fj-config](https://www.npmjs.org/package/fj-config) Looks incomplete. 99 | - [express-config](https://www.npmjs.org/package/express-config) NODE_ENV. package.json incomplete. 100 | - ...patience ran out 101 | 102 | [![Build Status](https://semaphoreci.com/api/v1/focusaurus/config3/branches/master/badge.svg)](https://semaphoreci.com/focusaurus/config3) 103 | --------------------------------------------------------------------------------