Compare commits

...

78 Commits

Author SHA1 Message Date
Jonatan Nilsson 296a65dfe1 2.1.0 2021-10-14 09:53:17 +00:00
Jonatan Nilsson 2ee285e1c4 argv: Add support for boolean flags when equal sign is used 2021-10-14 09:52:56 +00:00
Jonatan Nilsson 129e50367d circleci: Switch to alpine and add more versions 2021-06-23 09:41:27 +00:00
Jonatan Nilsson f43d81a2cd Merge branch 'master' of https://github.com/nfp-projects/nconf-lite 2021-06-23 09:39:42 +00:00
Jonatan Nilsson fa46b3b043 Fix ci and update engine in package 2021-06-23 09:39:40 +00:00
Jonatan Nilsson 863fec827d Add .circleci/config.yml 2021-06-23 09:36:46 +00:00
Jonatan Nilsson 98980c3453
Update README.md 2021-06-22 19:25:39 +00:00
Jonatan Nilsson 1014cdec86 Finished completely rewriting both library and unit tests 2021-06-22 19:25:00 +00:00
Jonatan Nilsson 1104c0d3ad Migrating a bunch of tests to ecmamodule 2021-06-07 08:58:26 +00:00
Jonatan Nilsson 44a22ce6dd Move async to main dependancy 2020-06-02 10:36:58 +00:00
Jonatan Nilsson b824493aa6 Updated version to 1.0.0, updated changelog 2020-06-02 10:23:51 +00:00
Jonatan Nilsson 2f66137a81 Update package and readme 2020-06-02 10:21:30 +00:00
Jonatan Nilsson c8a8d29d63 Add package-lock to ignore 2020-06-02 10:15:21 +00:00
Jonatan Nilsson 455103a330 Remove argv and its huge yargs dependancy 2020-06-02 10:15:06 +00:00
renovate[bot] aad2c1e449 [dist] Update dependency yargs to v14 (#334) 2019-11-14 14:56:06 -05:00
renovate[bot] ee8e89c1b0 [dist] Update dependency eslint to v6 (#330) 2019-06-23 21:33:55 -04:00
renovate[bot] 0ee6938568 [dist] Update dependency yargs to v13 (#321) 2019-06-14 22:33:09 -04:00
Eric Bickle 7d625086ca Fix saveToFile tests on Windows. indexzero#325 (#329) 2019-06-14 22:11:34 -04:00
Eric Bickle 85edc9afad [fix] Jest as devDependency indexzero/nconf#327 (#328) 2019-06-14 22:03:35 -04:00
Indospace.io 20e00efd6c Change require fs (#313)
what is there now is bad, everyone will think they need the fs library, when they only need it for the last example.
2019-05-16 17:05:20 -04:00
renovate[bot] e6f27f5d2c [dist] Update dependency jest to v24 (#320) 2019-05-16 17:03:06 -04:00
Matt Hamann d582066743
Upgraded encryption using CipherIV (#322)
* Remove package-lock.json from gitignore

* Update dependencies and fix repo url

* Fix test

* Update to cipheriv

* Bump version

* Sync package-lock

* Revert extraneous package changes

* Revert minor doc change
2019-05-15 23:27:38 -04:00
renovate[bot] 10318c0098 Configure Renovate (#319)
* Add renovate.json

* [dist] Update renovate.json
2019-05-14 14:09:08 -04:00
indexzero 48c92ce989 [dist tiny] Remove unused CircleCI configs. 2019-05-13 23:23:50 -04:00
indexzero 52afef5218 [tiny dist] Regenerate package-lock.json file. 2019-05-13 23:14:22 -04:00
Charlie Robbins 75641ffaee
[dist] Drop unsupported versions of Node. No automated publishing just yet. (#318) 2019-05-13 23:10:23 -04:00
Matt Hamann 1bb9d091f1 Switch to circleci (#317) 2019-05-13 22:56:47 -04:00
Adrien Becchis b8686aeff0 Migrating test to jest (#292)
* set up jest dependencies

* add parser option to eslint to support es6

* migrate first test to jest

* migrate the argv test to shpec

* convert the env and literal store tests

* convert the file-store tests

* convert the memory-store tests

* convert the hierarchy tests

* convert the provider-save test

* convert the complete test

* convert the provider test

* convert the conf test

* tweak a test that was no longer working (context changed)

* replace in place the helpers file

* remove vows dependency

* update the test invocation to rely on jest

* update the argv test to be able to use the jest --verbose option

* Some tweaks to the test to have them working

* Update node version tested (+10 +12 -9)

* Replace const by var until we drop 0.10/0.12/4 node

* Replace let by var until we drop 0.10/0.12/4 node
2019-04-29 22:26:02 -04:00
Matt Hamann 17376d4d17
Update packages to resolve security vulnerabilities (#306) 2018-09-27 13:03:17 -04:00
louis-murray 391665cc38 Enable writes env and argv stores with a flag (#285)
* added fucntionality to toggle readonly for env store

* fixed issue with using lint

* updated readme to reflect env options change

* updated tests to better test readOnly property

* added fucntionality to toggle readonly for env store

* fixed issue with using lint

* updated readme to reflect env options change

* updated tests to better test readOnly property

* updated tests to fix issues.

* updated argv to have readOnly toggle-able

* added tests for argv toggle-able readonly
2017-11-12 23:11:01 -05:00
Austin Burdine 6c5ba64db6 whitelist npm published files (#236)
- reduces files published to npm and downloaded with every install
2017-11-11 10:48:49 -05:00
Adrien Becchis c1e15681db Upgrading dependencies (#280)
* upgrade yargs and async dependencies.

were breaking change, but no impact according the tests

* upgrade yargs and package-lock.json

* add node 8 to the tested versions

* add node 8 to the allowed failures as requested

* add node 9 to travis build as acceptable failure and remove node 7
2017-11-06 11:15:12 -05:00
bryce-gibson b6699aba2d Don't do array lookups on strings. (#188)
`hasOwnProperty(number)` can return true for strings.

This is unlikely to be the desired usage, and can mean that odd
responses are returned by nconf.

Disable trying to check `hasOwnProperty` of strings.
2017-11-04 21:39:00 -04:00
Adrien Becchis 467ab753c8 Regex as env separator (#288)
* use regexp as env separator (support shorthand specification)

* add test to cover the env separator
2017-11-04 21:30:56 -04:00
Adrien Becchis e5db2ef6d7 Argv store separator (#291)
* argv store now accept a separator argument to create nested values

* remove stub file that shouldnt have been commited

* write a test to ensure separator is working well and use delete rather than undefined assign
2017-11-04 21:30:14 -04:00
Ahmed Ayoub 3607767f90 fixes #258 chainable .required() (#259)
* fixes #258 chainable .required()

* fixes #258 use var instead of let for travis builds
2017-11-04 00:34:27 -04:00
Matt Hamann bac910a6df 0.9.1 2017-11-03 23:52:19 -04:00
Adrien Becchis 2bdf7e1a32 Clean Argv Store options (#290)
* unset parse/transform from options and ensure they are not yargs options

* use delete rather than undefined assignement
2017-11-03 23:41:23 -04:00
Adrien Becchis b9321b200a transformer can now return an undefined key (#289) 2017-11-03 23:22:13 -04:00
Matt Hamann 81ce0be01e Update changelog 2017-10-31 20:20:27 -04:00
Augusto Franzoia b1ee63cfa4 fix error in transform function when dealing with dropped entries (#287) 2017-10-31 17:12:31 -04:00
Matt Hamann 9f70ba148f [doc] Update changelog 2017-10-30 11:27:11 -04:00
Matt Hamann 8afcf991eb [dist] Version bump. 0.9.0 2017-10-30 10:26:30 -04:00
Adrien Becchis b41c505c6e Save conf to dedicated file (#283)
* Added support for saving configuration to a specific file

Added support for saving configuration to a specific file

* add test to cover the save to file feature

* add posibility to specify a format to save to file

* add a test with nconf-yaml to ensure specifying a format works
2017-10-30 10:01:55 -04:00
Matt Hamann 52e0a3566e Update changelog 2017-10-28 21:16:03 -04:00
AdrieanKhisbe fa215a44f1 add tests for the normal configuration of yargs via argv 2017-10-28 17:13:38 -04:00
AdrieanKhisbe 802a8d623f test for yargs custom instance (more flexible check isYargs) 2017-10-28 17:13:38 -04:00
Nicolas Deveaud 3e26bb2756 Add posibility to pass a yargs instance to argv() method 2017-10-28 17:13:38 -04:00
Matt Hamann 856fdf8dff First pass at transform functions (#279) 2017-10-25 22:57:58 -04:00
Matt Hamann b9c345bf96 Fix `parseValues` option name 2017-10-21 21:58:30 -04:00
Matt Hamann 35088a3313 Added nconf.any method (#278)
* Added nconf.any method

Fixes #126
Implemented on the Provider class
Takes an array of keys, or a variable argument list
Supports both callback and non-callback invocations

* Use an explicit search base in file store test

Fixes #224
Test file was created under process.env.HOME, but test was searching in
the current working directory. If the cwd was not on the same drive as
the home directory, the test would fail.

* Added some API documentation to README for 'any'

* Tweak `.any` documentation
2017-10-21 16:42:24 -04:00
Matt Hamann ca10d0eaf8 Add basic linting rules 2017-10-21 16:01:06 -04:00
Brian Harrington bfb0220fe1 Remove unused module (#277)
crypto is no longer used in file.js
2017-10-21 15:47:41 -04:00
Matt Hamann 532ac9cc57 Support parsing simple values from env/argv strings (#273)
* simple parse, indexzero/nconf#72

* documentation for tryParse option

* Combine JSON parsing and simple parsing
2017-10-21 15:39:16 -04:00
Matt Hamann b8402d4eab Enable support for parsing JSON environment variables (#272)
* Add support for applying JSON string

* only take JSON Object or JSON Array into consideration

* Add tests and make JSON-parsing opt-in
2017-09-27 16:32:26 -04:00
Matt Hamann f46c449a9e 0.8.5 2017-08-15 23:59:31 -04:00
Matt Hamann 552300a687 [doc] Document lowerCase option in .env(options) (#268)
- Add missing documentation about the lowerCase option
2017-08-15 23:58:13 -04:00
evoye 5e8a34d6cf enable use with webpack by removing unnecessary fs call (#252) 2017-08-15 23:39:34 -04:00
Chris Manson 608b607782 Add test for merging with defaults (#255)
* implementing a test for merging with defaults

* bypassing strange common tests

* trying to fix travis build in node 7

* adding node 8 to tests

* removing node 8
2017-08-15 23:37:19 -04:00
Charlie Robbins 0c5774fec7 Merge pull request #211 from vanjan/fix-env-ignore-case
Copy `process.env` before lower-casing the keys
2016-08-25 12:48:47 -04:00
Charlie Robbins 011be49a5c Merge pull request #222 from katacarbix/patch-1
Fixed some issues with code escaping in README.md
2016-06-15 05:52:32 -04:00
Charter Jacobson d70b6a0335 Fixed some issues with code escaping 2016-05-14 14:55:23 -05:00
Jan Klosinski 392c6022c9 Copy `process.env` before lower-casing the keys
`process.env` is read-only in GitBash (and potentially other consoles),
so the `lowerCase` flag had no effect.
2016-03-01 17:08:35 +00:00
Jarrett Cruger 3d4e589578 [dist] Version bump. 0.8.4 2016-02-03 00:39:12 -08:00
Charlie Robbins b065c4ccd9 Merge pull request #209 from indexzero/better-DX
[fix] handle buffers
2016-02-02 21:46:14 -08:00
Jarrett Cruger 4431c33162 [fix] handle buffers so we dont get ambiguous errors when we dont strictly read the file as utf8 2016-02-02 16:38:48 -08:00
Jarrett Cruger 54cab2059a [dist] Version bump. 0.8.3 2016-02-01 22:26:12 -08:00
Charlie Robbins f554382435 Merge pull request #208 from indexzero/clean-secure
[fix] cleanup secure with new module
2016-02-02 01:24:49 -05:00
Jarrett Cruger b447268097 [fix] cleanup secure with new module 2016-02-01 22:21:15 -08:00
Jarrett Cruger 3d4682a62f Merge pull request #207 from moberemk/patch-1
Update README.md
2016-01-27 12:32:26 -05:00
Mark Oberemk de551d42ec Update README.md
Add basic documentation explaining the `nconf.required` method
2016-01-27 12:29:26 -05:00
Charlie Robbins 4a59c9b577 Merge pull request #191 from wturyn/feature-mandatory-keys
Feature: mandatory keys - required() method
2016-01-27 09:20:20 -08:00
Charlie Robbins e58d55429d Merge pull request #195 from AdrieanKhisbe/features/improved-ci
Improved CI
2015-12-22 14:21:07 -05:00
AdrieanKhisbe c242f777a6 [travis test] setup coveralls config + update README badges 2015-12-03 08:18:44 +01:00
AdrieanKhisbe c8dbede0ca [test] setup istanbul coverage 2015-12-03 08:03:12 +01:00
AdrieanKhisbe bdecdc29cf [travis] add v5 to node version tested 2015-12-03 07:48:52 +01:00
Wojtek Turyn 5d6e236b73 fixed 2015-10-15 10:44:26 +02:00
Wojtek Turyn 37a84ae8df required() method 2015-10-15 10:16:33 +02:00
61 changed files with 3257 additions and 3383 deletions

33
.circleci/config.yml Normal file
View File

@ -0,0 +1,33 @@
version: 2.1
commands:
test-nodejs:
steps:
- run:
name: Versions
command: npm version
- checkout
- run:
name: Install dependencies
command: npm install
- run:
name: Test
command: npm test
jobs:
node-v14:
docker:
- image: node:14-alpine
steps:
- test-nodejs
node-v16:
docker:
- image: node:16-alpine
steps:
- test-nodejs
workflows:
version: 2
node-multi-build:
jobs:
- node-v14
- node-v16

11
.gitignore vendored
View File

@ -1,11 +1,4 @@
.DS_Store
config.json
test/fixtures/*.json
!test/fixtures/complete.json
!test/fixtures/malformed.json
!test/fixtures/bom.json
!test/fixtures/no-bom.json
!test/fixtures/secure.json
node_modules/
node_modules/*
npm-debug.log
node_modules
npm-debug.log

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -1,25 +0,0 @@
sudo: false
language: node_js
node_js:
- "0.10"
- "0.12"
- "4.1"
before_install:
- travis_retry npm install npm -g
before_install:
- travis_retry npm install -g npm@2.5.1
- travis_retry npm install
script:
- npm test
matrix:
allow_failures:
- node_js: "0.10"
notifications:
email:
- travis@nodejitsu.com
irc: "irc.freenode.org#nodejitsu"

View File

@ -1,334 +1,12 @@
v0.8.0 / Sun, 20 Sep 2015
=========================
* [0922563](https://github.com/indexzero/nconf/commit/0922563) [doc fix] Remove unused and outdated literate coding documentation. (`indexzero`)
* [4b5030d](https://github.com/indexzero/nconf/commit/4b5030d) [fix] Only merge actual objects, not `null` values. Fixes #150. (`indexzero`)
* [a3589fa](https://github.com/indexzero/nconf/commit/a3589fa) Fixing provider issue in source (`Rob Rodriguez`)
* [51653e6](https://github.com/indexzero/nconf/commit/51653e6) Passing the value parameter to the providers (`Rob Rodriguez`)
* [2030144](https://github.com/indexzero/nconf/commit/2030144) [test dist] Add `test/fixtures/secure.json`. (`indexzero`)
* [9dbed2d](https://github.com/indexzero/nconf/commit/9dbed2d) [doc minor] Update docs for secure information. (`indexzero`)
* [0358545](https://github.com/indexzero/nconf/commit/0358545) [test api] Make the format capable of sub-objects. (`indexzero`)
* [04c0f3a](https://github.com/indexzero/nconf/commit/04c0f3a) [api test] Encrypt individual keys instead of entire stringified contents. Added basic unit tests. (`indexzero`)
* [d2b3561](https://github.com/indexzero/nconf/commit/d2b3561) [dist] Update `.travis.yml`. (`indexzero`)
* [442d2b4](https://github.com/indexzero/nconf/commit/442d2b4) [api] Allow for `secure` to be simply a secret string. (`indexzero`)
* [2de2bc0](https://github.com/indexzero/nconf/commit/2de2bc0) [api] Allow for "secure" option to be passed to `nconf.stores.File` to perform content encryption / decryption with `crypto.createCipher`. (`indexzero`)
* [5d95f13](https://github.com/indexzero/nconf/commit/5d95f13) filter out undefined values (`Christian Murphy`)
* [7d6be32](https://github.com/indexzero/nconf/commit/7d6be32) [travis] fix yaml syntax (supposed to solve nvm bugs #182) (`Joseph Page`)
* [abeeca0](https://github.com/indexzero/nconf/commit/abeeca0) [travis] fix npm bugs for node 0.8 (recommended way) (`Joseph Page`)
* [59056fe](https://github.com/indexzero/nconf/commit/59056fe) Update Async and ini (`Christian Murphy`)
* [a2b812f](https://github.com/indexzero/nconf/commit/a2b812f) Add travis tests for iojs (`Joseph Page`)
* [32d560c](https://github.com/indexzero/nconf/commit/32d560c) Add tests for node 0.12 (`Joseph Page`)
* [8a21ef3](https://github.com/indexzero/nconf/commit/8a21ef3) env({lowerCase:true}) option to make it possible to get() keys in lower case (`Olivier Lalonde`)
* [89dff39](https://github.com/indexzero/nconf/commit/89dff39) Quick grammar fix (`Nick Heiner`)
* [339e59a](https://github.com/indexzero/nconf/commit/339e59a) fix random fails on tests that use child process (`Pierre Beaujeu`)
* [a65e1a3](https://github.com/indexzero/nconf/commit/a65e1a3) update async (`Christian Murphy`)
* [a82b539](https://github.com/indexzero/nconf/commit/a82b539) update badge and use container build (`Christian Murphy`)
* [e5b33ce](https://github.com/indexzero/nconf/commit/e5b33ce) Add license attribute (`Gilad Peleg`)
0.7.2 / Tue, 4 Aug 2015
=======================
* [c2b8b97](https://github.com/indexzero/nconf/commit/c2b8b97) [dist] Version bump. 0.7.2 (`indexzero`)
* [3c11ef5](https://github.com/indexzero/nconf/commit/3c11ef5) fix: env.match test (`Remy Sharp`)
* [372521b](https://github.com/indexzero/nconf/commit/372521b) [doc] Add the badges!. (`indexzero`)
* [80ec01b](https://github.com/indexzero/nconf/commit/80ec01b) replace optimist with yargs (`Christian Murphy`)
* [6d86950](https://github.com/indexzero/nconf/commit/6d86950) Grammar nit (`Nick Heiner`)
v0.7.1 / Wed, 26 Nov 2014
=========================
* [dc6aed2](https://github.com/indexzero/nconf/commit/dc6aed2) [dist] Version bump. 0.7.1 (`Jarrett Cruger`)
* [87a3b82](https://github.com/indexzero/nconf/commit/87a3b82) [fix] we shouldnt be reversing here fixes #127 (`Jarrett Cruger`)
* [6271cdb](https://github.com/indexzero/nconf/commit/6271cdb) Revert "fixing the tests" (`Jarrett Cruger`)
* [f0d5b6e](https://github.com/indexzero/nconf/commit/f0d5b6e) [dist] Fix travis. (`indexzero`)
v0.7.0 / Wed, 26 Nov 2014
=========================
* [a2a1321](https://github.com/indexzero/nconf/commit/a2a1321) [dist] Version bump. 0.7.0 (`indexzero`)
* [352f075](https://github.com/indexzero/nconf/commit/352f075) [dist] "Real" CHANGELOG.md again. (`indexzero`)
* [af0e9fb](https://github.com/indexzero/nconf/commit/af0e9fb) [dist fix] Cleanup some whitespace. (`indexzero`)
* [0934255](https://github.com/indexzero/nconf/commit/0934255) [fix] Fixed regression introduced by #98. (`indexzero`)
* [8d5fb25](https://github.com/indexzero/nconf/commit/8d5fb25) [fix] Fix my own sloppy coding fixing the sloppy coding from #76. (`indexzero`)
* [f07bc40](https://github.com/indexzero/nconf/commit/f07bc40) [fix] Fix inconsistent style from #98. (`indexzero`)
* [0b8aa90](https://github.com/indexzero/nconf/commit/0b8aa90) [fix test] Remove leftover `console.log()` from #79. (`indexzero`)
* [f771500](https://github.com/indexzero/nconf/commit/f771500) [dist] Semantic cleanup from sloppy coding in #76. (`indexzero`)
* [ffce2cb](https://github.com/indexzero/nconf/commit/ffce2cb) [dist] Update package.json versions. (`indexzero`)
* [6301d7d](https://github.com/indexzero/nconf/commit/6301d7d) Update Readme; multiple file() needs custom key (`Mitchell McKenna`)
* [f69e43a](https://github.com/indexzero/nconf/commit/f69e43a) fixing the tests (`Chris Manson`)
* [c8b6c98](https://github.com/indexzero/nconf/commit/c8b6c98) Adding helpful information in case parsing failed. (`Martin Heidegger`)
* [8105c76](https://github.com/indexzero/nconf/commit/8105c76) [fix] only reverse keys for "get" action to be safe. (`Christopher Jeffrey`)
* [2241a36](https://github.com/indexzero/nconf/commit/2241a36) [fix] have latter stores precede the former stores again. (`Christopher Jeffrey`)
* [0bb89ee](https://github.com/indexzero/nconf/commit/0bb89ee) [fix] have latter stores precede the former stores. (`Christopher Jeffrey`)
* [43505a5](https://github.com/indexzero/nconf/commit/43505a5) Use ~ for dependencies (`Gabe Gorelick`)
* [05d73de](https://github.com/indexzero/nconf/commit/05d73de) [fix] No need to test 0.6 anymore (`Jarrett Cruger`)
* [79b9b84](https://github.com/indexzero/nconf/commit/79b9b84) [doc] Add a Literal example to add() (`Tommy Stanton`)
* [3a7b788](https://github.com/indexzero/nconf/commit/3a7b788) [doc] The store for File is empty if non-existent (`Tommy Stanton`)
* [9891814](https://github.com/indexzero/nconf/commit/9891814) Delete CHANGELOG.md (`Alexey Simonenko`)
* [120f5f0](https://github.com/indexzero/nconf/commit/120f5f0) added documentation (`joaoafrmartins`)
* [681fd2f](https://github.com/indexzero/nconf/commit/681fd2f) added regexp filtering to nconf env store (`joaoafrmartins`)
* [039057c](https://github.com/indexzero/nconf/commit/039057c) allow different separator for memorystore (`José F. Romaniello`)
* [b73b0e1](https://github.com/indexzero/nconf/commit/b73b0e1) attach help and showHelp arguments to the argv store (`Johnny Domino`)
* [4894c8f](https://github.com/indexzero/nconf/commit/4894c8f) resolves #64 passing usage string to optimist (`Johnny Domino`)
v0.6.9 / Sun, 1 Dec 2013
v2.0.0 / Tue, 2 Jun 2020
========================
* [022b9bc](https://github.com/indexzero/nconf/commit/022b9bc) [dist] Version bump. 0.6.9 (`Jarrett Cruger`)
* [9aa33b5](https://github.com/indexzero/nconf/commit/9aa33b5) [dist] bump optimist version, fixes #89 (`Jarrett Cruger`)
* [92311c8](https://github.com/indexzero/nconf/commit/92311c8) [rm] kill pkginfo (`Jarrett Cruger`)
* [c713936](https://github.com/indexzero/nconf/commit/c713936) [dist] bump async (`Jarrett Cruger`)
v0.6.8 / Tue, 29 Oct 2013
=========================
* [cd81efa](https://github.com/indexzero/nconf/commit/cd81efa) [dist] Version bump. 0.6.8 (`Jarrett Cruger`)
* [6c1eb5e](https://github.com/indexzero/nconf/commit/6c1eb5e) fixed white spacing and added (embarrassing absent) variable declarations (`midknight41`)
* [5546469](https://github.com/indexzero/nconf/commit/5546469) updated .travis.yml as travis doesn't support node 0.4 or 0.9 (`midknight41`)
* [29f1ca2](https://github.com/indexzero/nconf/commit/29f1ca2) added support for BOM in load() and loadSync() (`midknight41`)
* [ada15db](https://github.com/indexzero/nconf/commit/ada15db) Test that invalid file name is indicated (`Marcin Floryan`)
* [0135d95](https://github.com/indexzero/nconf/commit/0135d95) Additional error information when JSON config file cannot be read (`Marcin Floryan`)
* [5d2ebfb](https://github.com/indexzero/nconf/commit/5d2ebfb) Added test to confirm merging an Object and null behaves as expected. (`Michael Schoonmaker`)
* [ed41c51](https://github.com/indexzero/nconf/commit/ed41c51) Updated Memory.merge to handle null values (`Michael Schoonmaker`)
Completely redesigned and re-written with zero dependencies among other things.
v0.6.7 / Thu, 20 Dec 2012
=========================
* [d77c55d](https://github.com/flatiron/nconf/commit/d77c55d) [dist] Version bump. 0.6.7 (`indexzero`)
* [bb57c49](https://github.com/flatiron/nconf/commit/bb57c49) Prefer this fix for #65 to 6045618 (`Michael Hart`)
v0.6.6 / Thu, 20 Dec 2012
=========================
* [aec2b4e](https://github.com/flatiron/nconf/commit/aec2b4e) [dist] Version bump. 0.6.6 (`indexzero`)
* [6045618](https://github.com/flatiron/nconf/commit/6045618) [fix] Fix for #65 (`indexzero`)
* [0d795ec](https://github.com/flatiron/nconf/commit/0d795ec) [test] Better tests to show #65 (`indexzero`)
* [f19f0b6](https://github.com/flatiron/nconf/commit/f19f0b6) [test] Added failing test to illustrate #65 (`indexzero`)
* [bcbaf3a](https://github.com/flatiron/nconf/commit/bcbaf3a) [dist] Bump version to 0.6.5 (`Maciej Małecki`)
* [8b65e19](https://github.com/flatiron/nconf/commit/8b65e19) [test] Test on newer node versions (`Maciej Małecki`)
* [8e987b8](https://github.com/flatiron/nconf/commit/8e987b8) make it possible to use other formats than json in common.loadFiles and common.loadFilesSync (`Christian Tellnes`)
* [da39d3c](https://github.com/flatiron/nconf/commit/da39d3c) [fix] null values should merge properly instead of throwing errors (`Bradley Meck`)
* [7421836](https://github.com/flatiron/nconf/commit/7421836) [fix] heirarchy fixture file path wrong in tests (`Bradley Meck`)
* [683f789](https://github.com/flatiron/nconf/commit/683f789) [fix] #59 root get/set should work via null/undefined as key (`Bradley Meck`)
* [0f092ab](https://github.com/flatiron/nconf/commit/0f092ab) Added docs for options hash to optimist. (`Ethan Winn`)
v0.6.4 / Tue, 10 Jul 2012
=========================
* [7279bc1](https://github.com/flatiron/nconf/commit/7279bc1) [dist] Version bump. 0.6.4 (`indexzero`)
* [d96d254](https://github.com/flatiron/nconf/commit/d96d254) [fix] Fix regression introduced by 36e061c4bda8d79f657dc24b1dcf1937f31d7efe (`indexzero`)
* [7e8d9d6](https://github.com/flatiron/nconf/commit/7e8d9d6) [test] Added failing test for `.save()` regression introduced by @russfrank in 36e061c4bda8d79f657dc24b1dcf1937f31d7efe (`indexzero`)
* [04e2230](https://github.com/flatiron/nconf/commit/04e2230) [minor doc] Update file header in test/provider-test.js (`indexzero`)
v0.6.3 / Tue, 10 Jul 2012
=========================
* [c7c6b6f](https://github.com/flatiron/nconf/commit/c7c6b6f) [dist] Version bump. 0.6.3 (`indexzero`)
* [3073430](https://github.com/flatiron/nconf/commit/3073430) [api test doc] Make options to `Provider.prototype.file` take more flexible options (`indexzero`)
* [8b53c12](https://github.com/flatiron/nconf/commit/8b53c12) [minor] Use locally scoped `path` variable (`indexzero`)
v0.6.2 / Tue, 10 Jul 2012
=========================
* [80a7973](https://github.com/flatiron/nconf/commit/80a7973) [dist] Version bump. 0.6.2 (`indexzero`)
* [7515f66](https://github.com/flatiron/nconf/commit/7515f66) [fix] Ensure that all options are passed to `Provider.prototype.add` in `Provider.prototype.file`. Fixes #51 [doc] Update README.md and method documentation [dist] Remove vim comments (`indexzero`)
v0.6.1 / Sun, 8 Jul 2012
v1.0.0 / Tue, 2 Jun 2020
========================
* [eeddb70](https://github.com/flatiron/nconf/commit/eeddb70) [dist] Version bump. 0.6.1 (`indexzero`)
* [9aaafc5](https://github.com/flatiron/nconf/commit/9aaafc5) Ugh, fixed whitespace (`Michael Hart`)
* [3c08fad](https://github.com/flatiron/nconf/commit/3c08fad) Changed to as it's more accurate (`Michael Hart`)
* [e15f787](https://github.com/flatiron/nconf/commit/e15f787) Updated README and allowed a simpley syntax (`Michael Hart`)
* [92d4e9e](https://github.com/flatiron/nconf/commit/92d4e9e) Added test and updated docs (`Michael Hart`)
* [8921d05](https://github.com/flatiron/nconf/commit/8921d05) Added support for nested configs via env (`Michael Hart`)
* [6cbc323](https://github.com/flatiron/nconf/commit/6cbc323) Add reset to the list of destructive commands (`Michael Hart`)
* [26d81e8](https://github.com/flatiron/nconf/commit/26d81e8) Merge objects if necessary when traversing stores on get() (`Michael Hart`)
* [83440f9](https://github.com/flatiron/nconf/commit/83440f9) fix spelling in error message (`Christian Tellnes`)
* [87b0dd0](https://github.com/flatiron/nconf/commit/87b0dd0) [minor] Use `fs.exists` when available (`Maciej Małecki`)
* [1f67d35](https://github.com/flatiron/nconf/commit/1f67d35) [dist] Fix maintainers field (`Christian Howe`)
* [6353d02](https://github.com/flatiron/nconf/commit/6353d02) api and doc change for flatiron/nconf#28 (`.file` may now take a string instead of an object) (`Jonathan Stewmon`)
* [d3e6897](https://github.com/flatiron/nconf/commit/d3e6897) Proper teardowns in `complete-test.js` (`Russell Frank`)
* [94bdb7d](https://github.com/flatiron/nconf/commit/94bdb7d) Added `complete-test.js` & fixture. (`Russell Frank`)
* [36e061c](https://github.com/flatiron/nconf/commit/36e061c) Fixes to `Provider.save()` and tests. (`Russell Frank`)
* [29eb5f9](https://github.com/flatiron/nconf/commit/29eb5f9) [minor] Fix whitespaces (`Pavan Kumar Sunkara`)
* [6ce0b7a](https://github.com/flatiron/nconf/commit/6ce0b7a) Surfacing additional JSON.stringify arguments in formats.json.stringify, and adding the json_spacing option to the File constructor. (`Jordan Harband`)
* [b369931](https://github.com/flatiron/nconf/commit/b369931) [minor] Use `fs.existsSync` when available (`Maciej Małecki`)
* [d8c4749](https://github.com/flatiron/nconf/commit/d8c4749) [test] Test on `node@0.7` (`Maciej Małecki`)
* [464af41](https://github.com/flatiron/nconf/commit/464af41) [fix test] Fix bad test assertion (`indexzero`)
v0.5.1 / Mon, 2 Jan 2012
========================
* [6a6e092](https://github.com/flatiron/nconf/commit/6a6e092) [dist] Version bump. 0.5.1 (`indexzero`)
* [6242caa](https://github.com/flatiron/nconf/commit/6242caa) [api minor] Add `.loadSync()` to Memory store. Fixes #24 (`indexzero`)
* [d0a9121](https://github.com/flatiron/nconf/commit/d0a9121) [test dist] Remove unused `eyes` dependency (`indexzero`)
* [9e9e37b](https://github.com/flatiron/nconf/commit/9e9e37b) [minor] Update whitespace (`indexzero`)
* [fdb73f0](https://github.com/flatiron/nconf/commit/fdb73f0) updated tests to verify that Provider.load respects hierarchy (`Jonathan Stewmon`)
* [a216336](https://github.com/flatiron/nconf/commit/a216336) updated Provider.load to respect sources hierarchy (`Jonathan Stewmon`)
* [6b6bf85](https://github.com/flatiron/nconf/commit/6b6bf85) updated optimist to version 0.3.x (`Jonathan Stewmon`)
* [5c43d54](https://github.com/flatiron/nconf/commit/5c43d54) fixed merge issue in Provider.load by reversing store keys in getStores (`Jonathan Stewmon`)
* [2804b1f](https://github.com/flatiron/nconf/commit/2804b1f) fixed issue caused by using same name for defaults and overrides (`Jonathan Stewmon`)
* [e0e070a](https://github.com/flatiron/nconf/commit/e0e070a) [test] Test if `File.saveSync()` returns store content (`Maciej Małecki`)
* [963387c](https://github.com/flatiron/nconf/commit/963387c) [api] `File.saveSync()` should return store content (`Maciej Małecki`)
* [d5ce1ed](https://github.com/flatiron/nconf/commit/d5ce1ed) [test] Test `saveSync()` method of file store (`Maciej Małecki`)
* [cf9889e](https://github.com/flatiron/nconf/commit/cf9889e) [dist] Upgrade vows to 0.6.x (`Pavan Kumar Sunkara`)
This is quite the change as master had been updated over the years since the v.0.9.1 release. However it should be smaller and lighter.
v0.5.0 / Thu, 24 Nov 2011
=========================
* [62cb7fb](https://github.com/flatiron/nconf/commit/62cb7fb) [dist] Version bump. 0.5.0 (`indexzero`)
* [6c720ee](https://github.com/flatiron/nconf/commit/6c720ee) [dist] Update Copyright and Author to Nodejitsu Inc. (`indexzero`)
* [4643a14](https://github.com/flatiron/nconf/commit/4643a14) [doc] Updated README and added CHANGELOG.md (`indexzero`)
* [90b0297](https://github.com/flatiron/nconf/commit/90b0297) [test] Update tests to use optional options API (`indexzero`)
* [53d854a](https://github.com/flatiron/nconf/commit/53d854a) [api] Default to `options` if `options.store` is not available in nconf.Literal (`indexzero`)
* [b658f68](https://github.com/flatiron/nconf/commit/b658f68) [test] Add additional test coverage for hierarchical configuration (`indexzero`)
* [a9c3540](https://github.com/flatiron/nconf/commit/a9c3540) [fix test] Fix overwritten tests in file-store-test.js (`indexzero`)
* [f4f1fdf](https://github.com/flatiron/nconf/commit/f4f1fdf) [fix test] Update to respected `.sources` option correctly (`indexzero`)
* [bbcb271](https://github.com/flatiron/nconf/commit/bbcb271) [api fix] Dont eagerly create config files in `.load()` and `.loadSync()` (`indexzero`)
* [021850a](https://github.com/flatiron/nconf/commit/021850a) [test] Move around test .json files (`indexzero`)
* [0fbc9a2](https://github.com/flatiron/nconf/commit/0fbc9a2) [test] Added tests (which are now passing) for #15 (`indexzero`)
* [16a18bf](https://github.com/flatiron/nconf/commit/16a18bf) [refactor] Expose all store prototypes on `nconf.*`. Expose store instances on Provider.stores and Provider.sources (`indexzero`)
* [c3cebe7](https://github.com/flatiron/nconf/commit/c3cebe7) [refactor] Rename `.sources` to `._stores` and bring back `._sources` (`indexzero`)
* [78ce556](https://github.com/flatiron/nconf/commit/78ce556) [minor] Dont allow `.set()` calls to change values in readOnly stores: argv, env, and literal (`indexzero`)
* [1aa2f1f](https://github.com/flatiron/nconf/commit/1aa2f1f) [doc] Updated README.md (`indexzero`)
* [47a56cc](https://github.com/flatiron/nconf/commit/47a56cc) [test] Test for hierarchical argv options get() (`Sander Tolsma`)
* [c3c315d](https://github.com/flatiron/nconf/commit/c3c315d) [refactor] Refactor to make using nconf more fluent. (`indexzero`)
* [2c1ef71](https://github.com/flatiron/nconf/commit/2c1ef71) [dist] Bump to v0.4.6 (`Marak Squires`)
* [1b258bf](https://github.com/flatiron/nconf/commit/1b258bf) [fix] Fix option parsing (`Maciej Małecki`)
* [ef3222e](https://github.com/flatiron/nconf/commit/ef3222e) [dist] Make `repository` point to `flatiron/nconf` (`Maciej Małecki`)
v0.4.5 / Sun, 20 Nov 2011
=========================
* [f4723e9](https://github.com/flatiron/nconf/commit/f4723e9) [dist] Version bump. 0.4.5 (`indexzero`)
* [2475d06](https://github.com/flatiron/nconf/commit/2475d06) [test] Test command line arguments reparsing (`Maciej Małecki`)
* [bbc5885](https://github.com/flatiron/nconf/commit/bbc5885) [api] Reparse argv arguments on `system.loadArgv()` (`Maciej Małecki`)
* [51700ca](https://github.com/flatiron/nconf/commit/51700ca) [test minor] Use `process.argv[0]` when spawning processes (`Maciej Małecki`)
* [07f8c3e](https://github.com/flatiron/nconf/commit/07f8c3e) [doc] Add Travis build status image (`Maciej Małecki`)
* [bab96b0](https://github.com/flatiron/nconf/commit/bab96b0) [test] Add `.travis.yml` for testing on Travis CI (`Maciej Małecki`)
v0.4.4 / Sat, 22 Oct 2011
=========================
* [b96151e](https://github.com/flatiron/nconf/commit/b96151e) [dist] Version bump. 0.4.4 (`indexzero`)
* [d8a3020](https://github.com/flatiron/nconf/commit/d8a3020) [fix] filename --> file in a few file transport examples (`Joshua Holbrook`)
* [2e33082](https://github.com/flatiron/nconf/commit/2e33082) [api] Automatically search for a file if `options.search` is true in File store (`indexzero`)
v0.4.3 / Sun, 25 Sep 2011
=========================
* [86e22cb](https://github.com/flatiron/nconf/commit/86e22cb) [dist] Version bump. 0.4.3 (`indexzero`)
* [a2464d2](https://github.com/flatiron/nconf/commit/a2464d2) [api] Load sources into the default system store so they are permenantly cached (`indexzero`)
v0.4.2 / Sun, 25 Sep 2011
=========================
* [e243b0b](https://github.com/flatiron/nconf/commit/e243b0b) [dist] Version bump. 0.4.2 (`indexzero`)
* [d0aee0d](https://github.com/flatiron/nconf/commit/d0aee0d) [api test] Added `.sources` option for `nconf.Provider` for readonly configuration data (`indexzero`)
* [0234e17](https://github.com/flatiron/nconf/commit/0234e17) [fix] Update bad variable reference (`indexzero`)
v0.4.1 / Mon, 19 Sep 2011
=========================
* [d334d07](https://github.com/flatiron/nconf/commit/d334d07) [dist] Version bump. 0.4.1 (`indexzero`)
* [a490c77](https://github.com/flatiron/nconf/commit/a490c77) [fix] Match case in `require` statements (`indexzero`)
v0.4.0 / Sun, 18 Sep 2011
=========================
* [0addce4](https://github.com/flatiron/nconf/commit/0addce4) [dist] Version bump. 0.4.0 (`indexzero`)
* [c4c8d7b](https://github.com/flatiron/nconf/commit/c4c8d7b) [doc] Updated docco docs (`indexzero`)
* [f867e74](https://github.com/flatiron/nconf/commit/f867e74) [dist] Remove unused test fixtures (`indexzero`)
* [1ef5797](https://github.com/flatiron/nconf/commit/1ef5797) [api test] Finished API and tests for hierarchical configuration storage. (`indexzero`)
* [7ef9b11](https://github.com/flatiron/nconf/commit/7ef9b11) [doc] Minor update to library `title` (`indexzero`)
* [a063880](https://github.com/flatiron/nconf/commit/a063880) [doc] Updated usage.js and README.md for the next hierarchical syntax. (`indexzero`)
* [da2da7a](https://github.com/flatiron/nconf/commit/da2da7a) [api test breaking refactor] Significant refactor to how nconf works. Now a fully hierarchical configuration storage mechanism capable of multiple levels of stores of the same type. (`indexzero`)
* [2bda7b6](https://github.com/flatiron/nconf/commit/2bda7b6) [api] Added `nconf.stores.System` (`indexzero`)
v0.3.1 / Mon, 29 Aug 2011
=========================
* [54ea095](https://github.com/flatiron/nconf/commit/54ea095) [dist] Version bump. 0.3.1 (`indexzero`)
* [e631d23](https://github.com/flatiron/nconf/commit/e631d23) [fix] Lazy-load any CLI arguments from `optimist` (`indexzero`)
v0.3.0 / Sun, 28 Aug 2011
=========================
* [8a31728](https://github.com/flatiron/nconf/commit/8a31728) [dist] Version bump. 0.3.0 (`indexzero`)
* [2e47d02](https://github.com/flatiron/nconf/commit/2e47d02) [doc] Updated README.md (`indexzero`)
* [954b5fd](https://github.com/flatiron/nconf/commit/954b5fd) [doc] Updated docco docs (`indexzero`)
* [fb392dd](https://github.com/flatiron/nconf/commit/fb392dd) [api test] Updated test/provider-test.js and associated merge implementation (`indexzero`)
* [e8904e9](https://github.com/flatiron/nconf/commit/e8904e9) [api] Added `nconf.loadFiles()` method (`indexzero`)
* [a6533aa](https://github.com/flatiron/nconf/commit/a6533aa) [dist api test] Finished integrating features from reconf and updating associated tests (`indexzero`)
* [add8922](https://github.com/flatiron/nconf/commit/add8922) [api dist] Begin to integrate features from reconf (`indexzero`)
* [57f0742](https://github.com/flatiron/nconf/commit/57f0742) [doc] Update README.md for nconf-redis (`indexzero`)
v0.2.0 / Fri, 8 Jul 2011
========================
* [b6adab2](https://github.com/flatiron/nconf/commit/b6adab2) [dist] Version bump. 0.2.0 (`indexzero`)
* [8620e6b](https://github.com/flatiron/nconf/commit/8620e6b) [api test] Remove Redis store in preparation for nconf-redis (`indexzero`)
* [49a1a6d](https://github.com/flatiron/nconf/commit/49a1a6d) [dist] Added LICENSE (MIT ftw) (`indexzero`)
0.1.14 / Sat, 25 Jun 2011
=========================
* [d485f5e](https://github.com/flatiron/nconf/commit/d485f5e) [dist] Version bump. 0.1.14 (`indexzero`)
* [7e4623e](https://github.com/flatiron/nconf/commit/7e4623e) [api test] Update `nconf.Provider` to create a new instance of the store if the options are different (`indexzero`)
v0.1.13 / Fri, 24 Jun 2011
==========================
* [1b0f347](https://github.com/flatiron/nconf/commit/1b0f347) [dist] Version bump. 0.1.13 (`indexzero`)
* [d8b5a80](https://github.com/flatiron/nconf/commit/d8b5a80) [minor] Small style updates to the File store (`indexzero`)
* [c436851](https://github.com/flatiron/nconf/commit/c436851) [refactor]: Cleaned up error handling on File.loadSync and File.load [refactor]: Using path module to determine if file exists instead of throwing error [api]: File.load and File.loadSync will now automatically create the requested JSON file path if no file is found. (`Marak Squires`)
* [6c6887a](https://github.com/flatiron/nconf/commit/6c6887a) move callback outside of try / catch (`Dominic Tarr`)
v0.1.12 / Wed, 8 Jun 2011
=========================
* [ae5aec6](https://github.com/flatiron/nconf/commit/ae5aec6) [dist] Version bump. 0.1.12 (`indexzero`)
* [76db254](https://github.com/flatiron/nconf/commit/76db254) [fix test] Update nconf.stores.File to respond with an error when loading malformed JSON async (`indexzero`)
v0.1.11 / Tue, 7 Jun 2011
=========================
* [d7495f8](https://github.com/flatiron/nconf/commit/d7495f8) [dist] Version bump. 0.1.11 (`indexzero`)
* [4c7aea9](https://github.com/flatiron/nconf/commit/4c7aea9) [doc] Update docco docs (`indexzero`)
* [f611066](https://github.com/flatiron/nconf/commit/f611066) [dist] Update to pkginfo 0.2.0 (`indexzero`)
v0.1.10 / Sun, 5 Jun 2011
=========================
* [be76887](https://github.com/flatiron/nconf/commit/be76887) [dist] Version bump. 0.1.10 (`indexzero`)
* [7ffbf0a](https://github.com/flatiron/nconf/commit/7ffbf0a) [doc] Regenerate docco docs (`indexzero`)
* [13f5753](https://github.com/flatiron/nconf/commit/13f5753) [minor] Update `nconf.version` to use pkginfo (`indexzero`)
* [c9e60d9](https://github.com/flatiron/nconf/commit/c9e60d9) [doc] Update code docs (`indexzero`)
* [4459ba5](https://github.com/flatiron/nconf/commit/4459ba5) [api] Added `.merge()` to stores.Memory and stores.Redis (`indexzero`)
* [a4f00be](https://github.com/flatiron/nconf/commit/a4f00be) [dist] Update package.json and .gitignore (`indexzero`)
* [8a79ef0](https://github.com/flatiron/nconf/commit/8a79ef0) test retrieving non-existent keys and drilling into non-objects (`Sami Samhuri`)
* [6acc1fc](https://github.com/flatiron/nconf/commit/6acc1fc) allow storing null in redis (`Sami Samhuri`)
* [faa8ab9](https://github.com/flatiron/nconf/commit/faa8ab9) correctly retrieve falsy values from memory (hence file) (`Sami Samhuri`)
* [bdf2fc8](https://github.com/flatiron/nconf/commit/bdf2fc8) [fix] Fixed spelling error (`avian`)
* [e7c216e](https://github.com/flatiron/nconf/commit/e7c216e) [minor] Clarified error message returned when a config file contains invalid JSON. (`avian`)
* [e26bbe2](https://github.com/flatiron/nconf/commit/e26bbe2) [doc] Updated code samples for GitHub flavored markdown with Javascript (`indexzero`)
v0.1.9 / Mon, 16 May 2011
=========================
* [78202ec](https://github.com/flatiron/nconf/commit/78202ec) [dist] Version bump. 0.1.9 (`indexzero`)
* [87351ca](https://github.com/flatiron/nconf/commit/87351ca) [fix] Use the memory engine by default (`indexzero`)
v0.1.8 / Mon, 16 May 2011
=========================
* [badbb59](https://github.com/flatiron/nconf/commit/badbb59) [dist] Version bump. 0.1.8 (`indexzero`)
* [9da37df](https://github.com/flatiron/nconf/commit/9da37df) [dist api test] Refactor pluggable nconf-level logic into nconf.Provider. Update .gitignore for npm 1.0. Update pathing in source and tests to be more `require.paths` future-proof (`indexzero`)
v0.1.7 / Wed, 20 Apr 2011
=========================
* [4a61560](https://github.com/flatiron/nconf/commit/4a61560) [dist] Version bump. 0.1.7 (`indexzero`)
* [3b104f2](https://github.com/flatiron/nconf/commit/3b104f2) [doc] Update docco docs (`indexzero`)
* [d65922d](https://github.com/flatiron/nconf/commit/d65922d) [api] Add `.saveSync()` and `.loadSync()` methods to File store (`indexzero`)
v0.1.6 / Tue, 19 Apr 2011
=========================
* [b9951b4](https://github.com/flatiron/nconf/commit/b9951b4) [dist] Version bump. 0.1.6. (`indexzero`)
* [da85594](https://github.com/flatiron/nconf/commit/da85594) [doc] Update docco docs (`indexzero`)
* [067d58a](https://github.com/flatiron/nconf/commit/067d58a) [minor test] Add tests for File store `save()`. Improve default file format to pretty print the JSON output (`indexzero`)
v0.1.5 / Wed, 13 Apr 2011
=========================
* [96859f9](https://github.com/flatiron/nconf/commit/96859f9) [dist] Version bump. 0.1.5 (`indexzero`)
* [d99ab32](https://github.com/flatiron/nconf/commit/d99ab32) [fix] Dont allow `async.forEach` to be called on undefined or null arrays (`indexzero`)
v0.1.4 / Tue, 5 Apr 2011
========================
* [7484fdb](https://github.com/flatiron/nconf/commit/7484fdb) [dist] Version bump. 0.1.4 (`indexzero`)
* [04a59e9](https://github.com/flatiron/nconf/commit/04a59e9) [fix] Supress errors from Redis (`indexzero`)
v0.1.3 / Tue, 5 Apr 2011
========================
* [9bd6e26](https://github.com/flatiron/nconf/commit/9bd6e26) [dist] Version bump. 0.1.3 (`indexzero`)
* [4094125](https://github.com/flatiron/nconf/commit/4094125) [api] Add support for Redis auth and optional callbacks. (`indexzero`)
v0.1.2 / Sun, 3 Apr 2011
========================
* [81e1883](https://github.com/flatiron/nconf/commit/81e1883) [dist] Version bump. 0.1.2 (`indexzero`)
* [b850ae2](https://github.com/flatiron/nconf/commit/b850ae2) [fix] Update path to require statement in Redis store (`indexzero`)
v0.1.1 / Sat, 2 Apr 2011
========================
* [6f16bc7](https://github.com/flatiron/nconf/commit/6f16bc7) [dist] Version bump. 0.1.1 (`indexzero`)
* [752bb98](https://github.com/flatiron/nconf/commit/752bb98) [api] Improve the `.use()` method. Use the memory engine by default (`indexzero`)
* Major change: Removed all dependancies to yargs and removed argv support

217
README.md
View File

@ -1,24 +1,24 @@
# nconf
# nconf-lite
[![Version npm](https://img.shields.io/npm/v/nconf.svg?style=flat-square)](https://www.npmjs.com/package/nconf)[![npm Downloads](https://img.shields.io/npm/dm/nconf.svg?style=flat-square)](https://www.npmjs.com/package/nconf)[![Build Status](https://img.shields.io/travis/indexzero/nconf/master.svg?style=flat-square)](https://travis-ci.org/indexzero/nconf)[![Dependencies](https://img.shields.io/david/indexzero/nconf.svg?style=flat-square)](https://david-dm.org/indexzero/nconf)
Nconf-lite is a complete re-written design of original nconf with zero dependancy, tiny and fast while maintaining most if not all of the documented features of the old nconf.
Hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.
It is a hierarchical node.js configuration with files, environment variables, and atomic object merging.
Compared to nconf running at 952KB with over 220 files *installed*, nconf-lite is clocking at measly 42KB with only 11 files of easily reviewable code and a ton more unit test, testing almost every micro functionality.
## Example
Using nconf is easy; it is designed to be a simple key-value store with support for both local and remote storage. Keys are namespaced and delimited by `:`. Let's dive right into sample usage:
``` js
var fs = require('fs'),
nconf = require('nconf');
import Nconf from 'nconf-lite'
const nconf = new Nconf()
//
// Setup nconf to use (in-order):
// 1. Command-line arguments
// 2. Environment variables
// 3. A file located at 'path/to/config.json'
//
nconf.argv()
.env()
nconf.env()
.file({ file: 'path/to/config.json' });
//
@ -38,23 +38,18 @@ Using nconf is easy; it is designed to be a simple key-value store with support
//
// Save the configuration object to disk
//
nconf.save(function (err) {
fs.readFile('path/to/your/config.json', function (err, data) {
console.dir(JSON.parse(data.toString()))
});
});
nconf.save()
```
If you run the above script:
``` bash
$ NODE_ENV=production sample.js --foo bar
$ NODE_ENV=production sample.js
```
The output will be:
```
foo: bar
NODE_ENV: production
database: { host: '127.0.0.1', port: 5984 }
```
@ -63,7 +58,6 @@ The output will be:
Configuration management can get complicated very quickly for even trivial applications running in production. `nconf` addresses this problem by enabling you to setup a hierarchy for different sources of configuration with no defaults. **The order in which you attach these configuration sources determines their priority in the hierarchy.** Let's take a look at the options available to you
1. **nconf.argv(options)** Loads `process.argv` using yargs. If `options` is supplied it is passed along to yargs.
2. **nconf.env(options)** Loads `process.env` into the hierarchy.
3. **nconf.file(options)** Loads the configuration data at options.file into the hierarchy.
4. **nconf.defaults(options)** Loads the data in options.store into the hierarchy.
@ -72,7 +66,8 @@ Configuration management can get complicated very quickly for even trivial appli
A sane default for this could be:
``` js
var nconf = require('nconf');
import Nconf from 'nconf-lite'
const nconf = new Nconf()
//
// 1. any overrides
@ -83,9 +78,8 @@ A sane default for this could be:
//
// 2. `process.env`
// 3. `process.argv`
//
nconf.env().argv();
nconf.env();
//
// 4. Values in `config.json`
@ -98,16 +92,6 @@ A sane default for this could be:
//
nconf.file('custom', '/path/to/config.json');
//
// Or searching from a base directory.
// Note: `name` is optional.
//
nconf.file(name, {
file: 'config.json',
dir: 'search/from/here',
search: true
});
//
// 5. Any default values
//
@ -118,37 +102,55 @@ A sane default for this could be:
## API Documentation
The top-level of `nconf` is an instance of the `nconf.Provider` abstracts this all for you into a simple API.
### nconf.add(name, options)
Adds a new store with the specified `name` and `options`. If `options.type` is not set, then `name` will be used instead:
### nconf.any(names, callback)
Given a set of key names, gets the value of the first key found to be truthy. The key names can be given as separate arguments
or as an array. If the last argument is a function, it will be called with the result; otherwise, the value is returned.
``` js
nconf.add('supplied', { type: 'literal', store: { 'some': 'config' });
nconf.add('user', { type: 'file', file: '/path/to/userconf.json' });
nconf.add('global', { type: 'file', file: '/path/to/globalconf.json' });
//
// Get one of 'NODEJS_PORT' and 'PORT' as a return value
//
let port = nconf.any('NODEJS_PORT', 'PORT');
```
### nconf.use(name, options)
Similar to `nconf.add`, except that it can replace an existing store if new options are provided
### nconf.use(name)
Fetch a specific store with the specified name.
``` js
//
// Load a file store onto nconf with the specified settings
//
nconf.use('file', { file: '/path/to/some/config-file.json' });
nconf.file('custom', '/path/to/config.json');
//
// Replace the file store with new settings
// Grab the instance and set it to be readonly
//
nconf.use('file', { file: 'path/to/a-new/config-file.json' });
nconf.use('custom').readOnly = true
```
### nconf.remove(name)
Removes the store with the specified `name.` The configuration stored at that level will no longer be used for lookup(s).
### nconf.required(keys)
Declares a set of string keys to be mandatory, and throw an error if any are missing.
``` js
nconf.remove('file');
nconf.defaults({
keya: 'a',
});
nconf.required(['keya', 'keyb']);
// Error: Missing required keys: keyb
```
You can also chain `.required()` calls when needed. for example when a configuration depends on another configuration store
```js
config
.env()
.required([ 'STAGE']) //here you should have STAGE otherwise throw an error
.file( 'stage', path.resolve( 'configs', 'stages', config.get( 'STAGE' ) + '.json' ) )
.required([ 'OAUTH:redirectURL']) // here you should have OAUTH:redirectURL, otherwise throw an error
.file( 'oauth', path.resolve( 'configs', 'oauth', config.get( 'OAUTH:MODE' ) + '.json' ) )
.file( 'app', path.resolve( 'configs', 'app.json' ) )
.required([ 'LOGS_MODE']) // here you should haveLOGS_MODE, otherwise throw an error
.literal( 'logs', require( path.resolve( 'configs', 'logs', config.get( 'LOGS_MODE' ) + '.js') ))
.defaults( defaults );
```
## Storage Engines
@ -160,25 +162,40 @@ A simple in-memory storage engine that stores a nested JSON representation of th
nconf.use('memory');
```
### Argv
Responsible for loading the values parsed from `process.argv` by `yargs` into the configuration hierarchy. See the [yargs option docs](https://github.com/bcoe/yargs#optionskey-opt) for more on the option format.
``` js
//
// Can optionally also be an object literal to pass to `yargs`.
//
nconf.argv({
"x": {
alias: 'example',
describe: 'Example description for usage generation',
demand: true,
default: 'some-value'
}
});
```
### Env
Responsible for loading the values parsed from `process.env` into the configuration hierarchy.
By default, the env variables values are loaded into the configuration as strings.
#### Options
##### `lowerCase: {true|false}` (default: `false`)
Convert all input keys to lower case. Values are not modified.
If this option is enabled, all calls to `nconf.get()` must pass in a lowercase string (e.g. `nconf.get('port')`)
##### `parseValues: {true|false}` (default: `false`)
Attempt to parse well-known values (e.g. 'false', 'true', 'null', 'undefined', '3', '5.1' and JSON values)
into their proper types. If a value cannot be parsed, it will remain a string.
##### `transform: function(obj)`
Pass each key/value pair to the specified function for transformation.
The input `obj` contains two properties passed in the following format:
```
{
key: '<string>',
value: '<string>'
}
```
The transformation function may alter both the key and the value.
The function may return either an object in the asme format as the input or a value that evaluates to false.
If the return value is falsey, the entry will be dropped from the store, otherwise it will replace the original key/value.
*Note: If the return value doesn't adhere to the above rules, an exception will be thrown.*
#### Examples
``` js
//
@ -189,9 +206,22 @@ Responsible for loading the values parsed from `process.env` into the configurat
//
// Can also specify a separator for nested keys (instead of the default ':')
//
nconf.env('__');
nconf.env({ separator: '__' });
// Get the value of the env variable 'database__host'
var dbHost = nconf.get('database:host');
let dbHost = nconf.get('database:host');
//
// Can also lowerCase keys.
// Especially handy when dealing with environment variables which are usually
// uppercased.
//
// Given an environment variable PORT=3001
nconf.env();
let port = nconf.get('port') // undefined
nconf.env({ lowerCase: true });
let port = nconf.get('port') // 3001
//
// Or use all options
@ -199,9 +229,17 @@ Responsible for loading the values parsed from `process.env` into the configurat
nconf.env({
separator: '__',
match: /^whatever_matches_this_will_be_whitelisted/
whitelist: ['database__host', 'only', 'load', 'these', 'values', 'if', 'whatever_doesnt_match_but_is_whitelisted_gets_loaded_too']
whitelist: ['database__host', 'only', 'load', 'these', 'values', 'if', 'whatever_doesnt_match_but_is_whitelisted_gets_loaded_too'],
lowerCase: true,
parseValues: true,
transform: function(obj) {
if (obj.key === 'foo') {
obj.value = 'baz';
}
return obj;
}
});
var dbHost = nconf.get('database:host');
let dbHost = nconf.get('database:host');
```
### Literal
@ -214,7 +252,7 @@ Loads a given object literal into the configuration hierarchy. Both `nconf.defau
```
### File
Based on the Memory store, but provides additional methods `.save()` and `.load()` which allow you to read your configuration to and from file. As with the Memory store, all method calls are synchronous with the exception of `.save()` and `.load()` which take callback functions.
Based on the Memory store, but provides additional methods `.save()` and `.load()` which allow you to read your configuration to and from file. As with the Memory store, all method calls are synchronous includ `.save()` and `.load()`.
It is important to note that setting keys in the File engine will not be persisted to disk until a call to `.save()` is made. Note a custom key must be supplied as the first parameter for hierarchy to work if multiple files are used.
@ -223,6 +261,10 @@ It is important to note that setting keys in the File engine will not be persist
// add multiple files, hierarchically. notice the unique key for each file
nconf.file('user', 'path/to/your/user.json');
nconf.file('global', 'path/to/your/global.json');
// Set a variable in the user store and save it
nconf.user('user').set('some:variable', true)
nconf.user('user').save()
```
The file store is also extensible for multiple file formats, defaulting to `JSON`. To use a custom format, simply pass a format object to the `.use()` method. This object must have `.parse()` and `.stringify()` methods just like the native `JSON` object.
@ -231,7 +273,7 @@ If the file does not exist at the provided path, the store will simply be empty.
#### Encrypting file contents
As of `nconf@0.8.0` it is now possible to encrypt and decrypt file contents using the `secure` option:
Encryption and decrypting file contents using the `secure` option:
``` js
nconf.file('secure-file', {
@ -243,46 +285,26 @@ nconf.file('secure-file', {
})
```
This will encrypt each key using [`crypto.createCipher`](https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password), defaulting to `aes-256-ctr`. The encrypted file contents will look like this:
This will encrypt each key using [`crypto.createCipheriv`](https://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv_options), defaulting to `aes-256-ctr`. The encrypted file contents will look like this:
```
{
"config-key-name": {
"alg": "aes-256-ctr", // cipher used
"value": "af07fbcf" // encrypted contents
"value": "af07fbcf", // encrypted contents
"iv": "49e7803a2a5ef98c7a51a8902b76dd10" // initialization vector
},
"another-config-key": {
"alg": "aes-256-ctr", // cipher used
"value": "e310f6d94f13" // encrypted contents
"value": "e310f6d94f13", // encrypted contents
"iv": "b654e01aed262f37d0acf200be193985" // initialization vector
},
}
```
### Redis
There is a separate Redis-based store available through [nconf-redis][0]. To install and use this store simply:
``` bash
$ npm install nconf
$ npm install nconf-redis
```
Once installing both `nconf` and `nconf-redis`, you must require both modules to use the Redis store:
``` js
var nconf = require('nconf');
//
// Requiring `nconf-redis` will extend the `nconf`
// module.
//
require('nconf-redis');
nconf.use('redis', { host: 'localhost', port: 6379, ttl: 60 * 60 * 1000 });
```
## Installation
```
npm install nconf --save
``` bash
npm install nconf-lite --save
```
## Run Tests
@ -292,7 +314,8 @@ Tests are written in vows and give complete coverage of all APIs and storage eng
$ npm test
```
#### Author: [Charlie Robbins](http://nodejitsu.com)
#### Original author: [Charlie Robbins](http://nodejitsu.com)
#### Rewriter of all that garbage: TheThing
#### License: MIT
[0]: http://github.com/indexzero/nconf-redis
[0]: http://github.com/nfp-projects/nconf-lite

177
lib/common.mjs Normal file
View File

@ -0,0 +1,177 @@
import fs from 'fs'
import Memory from './stores/memory.mjs'
//
// ### function validkeyvalue(key)
// #### @key {any} key to check
// Return string of key if valid string type key,
// otherwise transform into new key containing
// the error message
export function validkeyvalue(key) {
let type = typeof(key)
if (key && type !== 'string' && type !== 'number') {
return '__invalid_valuetype_of_' + type + '__'
}
return null
}
//
// ### function path (key)
// #### @key {string} The ':' delimited key to split
// Returns a fully-qualified path to a nested nconf key.
// If given null or undefined it should return an empty path.
// '' should still be respected as a path.
//
export function path(key, separator) {
let invalidType = validkeyvalue(key)
if (invalidType) {
return [invalidType]
}
separator = separator || ':'
return key == null
|| key === ''
? []
: key.toString().split(separator)
}
//
// ### function key (arguments)
// Returns a `:` joined string from the `arguments`.
//
export function key(...path) {
return path.map(function(item) {
return validkeyvalue(item) || ('' + item)
}).join(':')
}
//
// ### function key (arguments)
// Returns a joined string from the `arguments`,
// first argument is the join delimiter.
//
export function keyed(separator, ...path) {
return path.map(function(item) {
return validkeyvalue(item) || ('' + item)
}).join(separator)
}
// taken from isobject npm library
export function isObject(val) {
return val != null && typeof val === 'object' && Array.isArray(val) === false
}
// Return a new recursive deep instance of array of objects
// or values to make sure no original object ever get touched
export function mergeRecursiveArray(arr) {
return arr.map(function(item) {
if (isObject(item)) return mergeRecursive({}, item)
if (Array.isArray(item)) return mergeRecursiveArray(item)
return item
})
}
// Recursively merges the child into the parent.
export function mergeRecursive(parent, child) {
Object.keys(child).forEach(key => {
// Arrays will always overwrite for now
if (Array.isArray(child[key])) {
parent[key] = mergeRecursiveArray(child[key])
} else if (child[key] && typeof child[key] === 'object') {
// We don't wanna support cross merging between array and objects
// so we overwrite the old value (at least for now).
if (parent[key] && Array.isArray(parent[key])) {
parent[key] = mergeRecursive({}, child[key])
} else {
parent[key] = mergeRecursive(parent[key] || {}, child[key])
}
} else {
parent[key] = child[key]
}
})
return parent
}
//
// ### function merge (objs)
// #### @objs {Array} Array of object literals to merge
// Merges the specified `objs` together into a new object.
// This differs from the old logic as it does not affect or chagne
// any of the objects being merged.
//
export function merge(orgOut, orgObjs) {
let out = orgOut
let objs = orgObjs
if (objs === undefined) {
out = {}
objs = orgOut
}
if (!Array.isArray(objs)) {
throw new Error('merge called with non-array of objects')
}
for (let x = 0; x < objs.length; x++) {
out = mergeRecursive(out, objs[x])
}
return out
}
//
// ### function capitalize (str)
// #### @str {string} String to capitalize
// Capitalizes the specified `str` if string, otherwise
// returns the original object
//
export function capitalize(str) {
if (typeof(str) !== 'string' && typeof(str) !== 'number') {
return str
}
let out = str.toString()
return out && (out[0].toString()).toUpperCase() + out.slice(1)
}
//
// ### function parseValues (any)
// #### @any {string} String to parse as json or return as is
// try to parse `any` as a json stringified
//
export function parseValues(value) {
if (value === 'undefined') {
return undefined
}
try {
return JSON.parse(value)
} catch (ignore) {
return value
}
}
//
// ### function transform(map, fn)
// #### @map {object} Object of key/value pairs to apply `fn` to
// #### @fn {function} Transformation function that will be applied to every key/value pair
// transform a set of key/value pairs and return the transformed result
export function transform(map, fn) {
var pairs = Object.keys(map).map(function(key) {
var result = fn(key, map[key])
if (!result) {
return null
} else if (result.key) {
return result
}
throw new Error('Transform function passed to store returned an invalid format: ' + JSON.stringify(result))
})
return pairs
.filter(function(pair) {
return pair !== null
})
.reduce(function(accumulator, pair) {
accumulator[pair.key] = pair.value
return accumulator
}, {})
}

View File

@ -1,44 +0,0 @@
/*
* nconf.js: Top-level include for the nconf module
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
async = require('async'),
common = require('./nconf/common'),
Provider = require('./nconf/provider').Provider;
//
// `nconf` is by default an instance of `nconf.Provider`.
//
var nconf = module.exports = new Provider();
//
// Expose the version from the package.json
//
nconf.version = require('../package.json').version;
//
// Setup all stores as lazy-loaded getters.
//
fs.readdirSync(__dirname + '/nconf/stores').forEach(function (file) {
var store = file.replace('.js', ''),
name = common.capitalize(store);
nconf.__defineGetter__(name, function () {
return require('./nconf/stores/' + store)[name];
});
});
//
// Expose the various components included with nconf
//
nconf.key = common.key;
nconf.path = common.path;
nconf.loadFiles = common.loadFiles;
nconf.loadFilesSync = common.loadFilesSync;
nconf.formats = require('./nconf/formats');
nconf.Provider = Provider;

181
lib/nconf.mjs Normal file
View File

@ -0,0 +1,181 @@
import fs from 'fs'
import { fileURLToPath } from 'url'
import path from 'path'
import * as common from './common.mjs'
import Literal from './stores/literal.mjs'
import Memory from './stores/memory.mjs'
import File from './stores/file.mjs'
import Env from './stores/env.mjs'
import Argv from './stores/argv.mjs'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const pckg = JSON.parse(fs.readFileSync(path.resolve(path.join(__dirname, '../package.json'))))
const AvailableStores = [
['memory', Memory],
['file', File],
['defaults', Literal],
['overrides', Literal],
['literal', Literal],
['env', Env],
['argv', Argv],
]
function Nconf(options) {
let opts = options || {}
this.sources = []
this.using = new Map()
this.version = pckg.version
this.init()
}
Nconf.prototype.key = common.key
Nconf.prototype.path = common.path
Nconf.prototype.init = function() {
AvailableStores.forEach((storeType) => {
let nameCapital = common.capitalize(storeType[0])
let nameLower = storeType[0].toLowerCase()
Object.defineProperty(this, nameCapital, {
value: storeType[1],
writable: false,
enumerable: true,
})
Object.defineProperty(this, nameLower, {
value: function(leName, leOpts) {
let name = leName
let options = leOpts || {}
if (typeof(name) !== 'string') {
name = nameLower
options = leName || {}
}
this.add(name, new this[nameCapital](options))
return this
},
writable: false,
enumerable: true,
})
})
}
Nconf.prototype.any = function(...items) {
let check = items
if (items.length === 1 && Array.isArray(items[0])) {
check = items[0]
}
for (let i = 0; i < check.length; i++) {
let found = this.get(check[i])
if (found) return found
}
return undefined
}
Nconf.prototype.get = function(key) {
let out = []
for (let i = 0; i < this.sources.length; i++) {
let found = this.sources[i].get(key)
if (found && !out.length && (Array.isArray(found) || typeof(found) !== 'object')) {
return found
}
if (found) {
out.push(found)
}
}
if (!out.length) return undefined
return common.merge(out.reverse())
}
Nconf.prototype.set = function(key, value) {
for (let i = 0; i < this.sources.length; i++) {
if (!this.sources[i].readOnly) {
if (this.sources[i].set(key, value))
return this
}
}
return false
}
Nconf.prototype.clear = function(key) {
for (let i = 0; i < this.sources.length; i++) {
this.sources[i].clear(key)
}
if (this.get(key)) {
return false
}
return this
}
Nconf.prototype.load = function() {
for (let i = 0; i < this.sources.length; i++) {
if (typeof(this.sources[i].load) === 'function') {
this.sources[i].load()
}
}
}
Nconf.prototype.save = function() {
for (let i = 0; i < this.sources.length; i++) {
if (typeof(this.sources[i].save) === 'function') {
this.sources[i].save()
}
}
}
Nconf.prototype.reset = function() {
throw new Error('Deprecated, create new instance instead')
}
Nconf.prototype.required = function(...items) {
let check = items
if (items.length === 1 && Array.isArray(items[0])) {
check = items[0]
}
let missing = []
for (let i = 0; i < check.length; i++) {
if (!this.get(check[i])) {
missing.push(check[i])
}
}
if (missing.length) {
throw new Error('Missing required keys: ' + missing.join(', '));
}
return this
}
Nconf.prototype.add = function(name, store) {
let oldStore = this.using.get(name)
if (typeof(store.load) === 'function') {
store.load()
}
if (oldStore) {
this.sources.splice(this.sources.indexOf(oldStore), 1)
this.using.delete(name)
}
this.using.set(name, store)
this.sources.push(store)
}
Nconf.prototype.use = function(name) {
return this.using.get(name)
}
Nconf.register = function(name, val) {
AvailableStores.push([name, val])
let nameCapital = common.capitalize(name)
Object.defineProperty(Nconf, nameCapital, {
value: val,
writable: false,
enumerable: true,
})
}
AvailableStores.forEach((storeType) => {
let nameCapital = common.capitalize(storeType[0])
Object.defineProperty(Nconf, nameCapital, {
value: storeType[1],
writable: false,
enumerable: true,
})
})
export default Nconf

View File

@ -1,123 +0,0 @@
/*
* utils.js: Utility functions for the nconf module.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
async = require('async'),
formats = require('./formats'),
Memory = require('./stores/memory').Memory;
var common = exports;
//
// ### function path (key)
// #### @key {string} The ':' delimited key to split
// Returns a fully-qualified path to a nested nconf key.
// If given null or undefined it should return an empty path.
// '' should still be respected as a path.
//
common.path = function (key, separator) {
separator = separator || ':';
return key == null ? [] : key.split(separator);
};
//
// ### function key (arguments)
// Returns a `:` joined string from the `arguments`.
//
common.key = function () {
return Array.prototype.slice.call(arguments).join(':');
};
//
// ### function key (arguments)
// Returns a joined string from the `arguments`,
// first argument is the join delimiter.
//
common.keyed = function () {
return Array.prototype.slice.call(arguments, 1).join(arguments[0]);
};
//
// ### function loadFiles (files, callback)
// #### @files {Object|Array} List of files (or settings object) to load.
// #### @callback {function} Continuation to respond to when complete.
// Loads all the data in the specified `files`.
//
common.loadFiles = function (files, callback) {
if (!files) {
return callback(null, {});
}
var options = Array.isArray(files) ? { files: files } : files;
//
// Set the default JSON format if not already
// specified
//
options.format = options.format || formats.json;
function parseFile (file, next) {
fs.readFile(file, function (err, data) {
return !err
? next(null, options.format.parse(data.toString()))
: next(err);
});
}
async.map(options.files, parseFile, function (err, objs) {
return err ? callback(err) : callback(null, common.merge(objs));
});
};
//
// ### function loadFilesSync (files)
// #### @files {Object|Array} List of files (or settings object) to load.
// Loads all the data in the specified `files` synchronously.
//
common.loadFilesSync = function (files) {
if (!files) {
return;
}
//
// Set the default JSON format if not already
// specified
//
var options = Array.isArray(files) ? { files: files } : files;
options.format = options.format || formats.json;
return common.merge(options.files.map(function (file) {
return options.format.parse(fs.readFileSync(file, 'utf8'));
}));
};
//
// ### function merge (objs)
// #### @objs {Array} Array of object literals to merge
// Merges the specified `objs` using a temporary instance
// of `stores.Memory`.
//
common.merge = function (objs) {
var store = new Memory();
objs.forEach(function (obj) {
Object.keys(obj).forEach(function (key) {
store.merge(key, obj[key]);
});
});
return store.store;
};
//
// ### function capitalize (str)
// #### @str {string} String to capitalize
// Capitalizes the specified `str`.
//
common.capitalize = function (str) {
return str && str[0].toUpperCase() + str.slice(1);
};

View File

@ -1,28 +0,0 @@
/*
* formats.js: Default formats supported by nconf
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var ini = require('ini');
var formats = exports;
//
// ### @json
// Standard JSON format which pretty prints `.stringify()`.
//
formats.json = {
stringify: function (obj, replacer, spacing) {
return JSON.stringify(obj, replacer || null, spacing || 2)
},
parse: JSON.parse
};
//
// ### @ini
// Standard INI format supplied from the `ini` module
// http://en.wikipedia.org/wiki/INI_file
//
formats.ini = ini;

View File

@ -1,574 +0,0 @@
/*
* provider.js: Abstraction providing an interface into pluggable configuration storage.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var async = require('async'),
common = require('./common');
//
// ### function Provider (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Provider object responsible
// for exposing the pluggable storage features of `nconf`.
//
var Provider = exports.Provider = function (options) {
//
// Setup default options for working with `stores`,
// `overrides`, `process.env` and `process.argv`.
//
options = options || {};
this.stores = {};
this.sources = [];
this.init(options);
};
//
// Define wrapper functions for using basic stores
// in this instance
//
['argv', 'env'].forEach(function (type) {
Provider.prototype[type] = function () {
var args = [type].concat(Array.prototype.slice.call(arguments));
return this.add.apply(this, args);
};
});
//
// ### function file (key, options)
// #### @key {string|Object} Fully qualified options, name of file store, or path.
// #### @path {string|Object} **Optional** Full qualified options, or path.
// Adds a new `File` store to this instance. Accepts the following options
//
// nconf.file({ file: '.jitsuconf', dir: process.env.HOME, search: true });
// nconf.file('path/to/config/file');
// nconf.file('userconfig', 'path/to/config/file');
// nconf.file('userconfig', { file: '.jitsuconf', search: true });
//
Provider.prototype.file = function (key, options) {
if (arguments.length == 1) {
options = typeof key === 'string' ? { file: key } : key;
key = 'file';
}
else {
options = typeof options === 'string'
? { file: options }
: options;
}
options.type = 'file';
return this.add(key, options);
};
//
// Define wrapper functions for using
// overrides and defaults
//
['defaults', 'overrides'].forEach(function (type) {
Provider.prototype[type] = function (options) {
options = options || {};
if (!options.type) {
options.type = 'literal';
}
return this.add(type, options);
};
});
//
// ### function use (name, options)
// #### @type {string} Type of the nconf store to use.
// #### @options {Object} Options for the store instance.
// Adds (or replaces) a new store with the specified `name`
// and `options`. If `options.type` is not set, then `name`
// will be used instead:
//
// provider.use('file');
// provider.use('file', { type: 'file', filename: '/path/to/userconf' })
//
Provider.prototype.use = function (name, options) {
options = options || {};
var type = options.type || name;
function sameOptions (store) {
return Object.keys(options).every(function (key) {
return options[key] === store[key];
});
}
var store = this.stores[name],
update = store && !sameOptions(store);
if (!store || update) {
if (update) {
this.remove(name);
}
this.add(name, options);
}
return this;
};
//
// ### function add (name, options)
// #### @name {string} Name of the store to add to this instance
// #### @options {Object} Options for the store to create
// Adds a new store with the specified `name` and `options`. If `options.type`
// is not set, then `name` will be used instead:
//
// provider.add('memory');
// provider.add('userconf', { type: 'file', filename: '/path/to/userconf' })
//
Provider.prototype.add = function (name, options, usage) {
options = options || {};
var type = options.type || name;
if (!require('../nconf')[common.capitalize(type)]) {
throw new Error('Cannot add store with unknown type: ' + type);
}
this.stores[name] = this.create(type, options, usage);
if (this.stores[name].loadSync) {
this.stores[name].loadSync();
}
return this;
};
//
// ### function remove (name)
// #### @name {string} Name of the store to remove from this instance
// Removes a store with the specified `name` from this instance. Users
// are allowed to pass in a type argument (e.g. `memory`) as name if
// this was used in the call to `.add()`.
//
Provider.prototype.remove = function (name) {
delete this.stores[name];
return this;
};
//
// ### function create (type, options)
// #### @type {string} Type of the nconf store to use.
// #### @options {Object} Options for the store instance.
// Creates a store of the specified `type` using the
// specified `options`.
//
Provider.prototype.create = function (type, options, usage) {
return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options, usage);
};
//
// ### function init (options)
// #### @options {Object} Options to initialize this instance with.
// Initializes this instance with additional `stores` or `sources` in the
// `options` supplied.
//
Provider.prototype.init = function (options) {
var self = this;
//
// Add any stores passed in through the options
// to this instance.
//
if (options.type) {
this.add(options.type, options);
}
else if (options.store) {
this.add(options.store.name || options.store.type, options.store);
}
else if (options.stores) {
Object.keys(options.stores).forEach(function (name) {
var store = options.stores[name];
self.add(store.name || name || store.type, store);
});
}
//
// Add any read-only sources to this instance
//
if (options.source) {
this.sources.push(this.create(options.source.type || options.source.name, options.source));
}
else if (options.sources) {
Object.keys(options.sources).forEach(function (name) {
var source = options.sources[name];
self.sources.push(self.create(source.type || source.name || name, source));
});
}
};
//
// ### function get (key, callback)
// #### @key {string} Key to retrieve for this instance.
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Retrieves the value for the specified key (if any).
//
Provider.prototype.get = function (key, callback) {
if (typeof key === 'function') {
// Allow a * key call to be made
callback = key;
key = null;
}
//
// If there is no callback we can short-circuit into the default
// logic for traversing stores.
//
if (!callback) {
return this._execute('get', 1, key, callback);
}
//
// Otherwise the asynchronous, hierarchical `get` is
// slightly more complicated because we do not need to traverse
// the entire set of stores, but up until there is a defined value.
//
var current = 0,
names = Object.keys(this.stores),
self = this,
response,
mergeObjs = [];
async.whilst(function () {
return typeof response === 'undefined' && current < names.length;
}, function (next) {
var store = self.stores[names[current]];
current++;
if (store.get.length >= 2) {
return store.get(key, function (err, value) {
if (err) {
return next(err);
}
response = value;
// Merge objects if necessary
if (response && typeof response === 'object' && !Array.isArray(response)) {
mergeObjs.push(response);
response = undefined;
}
next();
});
}
response = store.get(key);
// Merge objects if necessary
if (response && typeof response === 'object' && !Array.isArray(response)) {
mergeObjs.push(response);
response = undefined;
}
next();
}, function (err) {
if (!err && mergeObjs.length) {
response = common.merge(mergeObjs.reverse());
}
return err ? callback(err) : callback(null, response);
});
};
//
// ### function set (key, value, callback)
// #### @key {string} Key to set in this instance
// #### @value {literal|Object} Value for the specified key
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Sets the `value` for the specified `key` in this instance.
//
Provider.prototype.set = function (key, value, callback) {
return this._execute('set', 2, key, value, callback);
};
//
// ### function reset (callback)
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Clears all keys associated with this instance.
//
Provider.prototype.reset = function (callback) {
return this._execute('reset', 0, callback);
};
//
// ### function clear (key, callback)
// #### @key {string} Key to remove from this instance
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Removes the value for the specified `key` from this instance.
//
Provider.prototype.clear = function (key, callback) {
return this._execute('clear', 1, key, callback);
};
//
// ### function merge ([key,] value [, callback])
// #### @key {string} Key to merge the value into
// #### @value {literal|Object} Value to merge into the key
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Merges the properties in `value` into the existing object value at `key`.
//
// 1. If the existing value `key` is not an Object, it will be completely overwritten.
// 2. If `key` is not supplied, then the `value` will be merged into the root.
//
Provider.prototype.merge = function () {
var self = this,
args = Array.prototype.slice.call(arguments),
callback = typeof args[args.length - 1] === 'function' && args.pop(),
value = args.pop(),
key = args.pop();
function mergeProperty (prop, next) {
return self._execute('merge', 2, prop, value[prop], next);
}
if (!key) {
if (Array.isArray(value) || typeof value !== 'object') {
return onError(new Error('Cannot merge non-Object into top-level.'), callback);
}
return async.forEach(Object.keys(value), mergeProperty, callback || function () { })
}
return this._execute('merge', 2, key, value, callback);
};
//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
// Responds with an Object representing all keys associated in this instance.
//
Provider.prototype.load = function (callback) {
var self = this;
function getStores () {
var stores = Object.keys(self.stores);
stores.reverse();
return stores.map(function (name) {
return self.stores[name];
});
}
function loadStoreSync(store) {
if (!store.loadSync) {
throw new Error('nconf store ' + store.type + ' has no loadSync() method');
}
return store.loadSync();
}
function loadStore(store, next) {
if (!store.load && !store.loadSync) {
return next(new Error('nconf store ' + store.type + ' has no load() method'));
}
return store.loadSync
? next(null, store.loadSync())
: store.load(next);
}
function loadBatch (targets, done) {
if (!done) {
return common.merge(targets.map(loadStoreSync));
}
async.map(targets, loadStore, function (err, objs) {
return err ? done(err) : done(null, common.merge(objs));
});
}
function mergeSources (data) {
//
// If `data` was returned then merge it into
// the system store.
//
if (data && typeof data === 'object') {
self.use('sources', {
type: 'literal',
store: data
});
}
}
function loadSources () {
var sourceHierarchy = self.sources.splice(0);
sourceHierarchy.reverse();
//
// If we don't have a callback and the current
// store is capable of loading synchronously
// then do so.
//
if (!callback) {
mergeSources(loadBatch(sourceHierarchy));
return loadBatch(getStores());
}
loadBatch(sourceHierarchy, function (err, data) {
if (err) {
return callback(err);
}
mergeSources(data);
return loadBatch(getStores(), callback);
});
}
return self.sources.length
? loadSources()
: loadBatch(getStores(), callback);
};
//
// ### function save (callback)
// #### @callback {function} **optional** Continuation to respond to when
// complete.
// Instructs each provider to save. If a callback is provided, we will attempt
// asynchronous saves on the providers, falling back to synchronous saves if
// this isn't possible. If a provider does not know how to save, it will be
// ignored. Returns an object consisting of all of the data which was
// actually saved.
//
Provider.prototype.save = function (value, callback) {
if (!callback && typeof value === 'function') {
callback = value;
value = null;
}
var self = this,
names = Object.keys(this.stores);
function saveStoreSync(memo, name) {
var store = self.stores[name];
//
// If the `store` doesn't have a `saveSync` method,
// just ignore it and continue.
//
if (store.saveSync) {
var ret = store.saveSync();
if (typeof ret == 'object' && ret !== null) {
memo.push(ret);
}
}
return memo;
}
function saveStore(memo, name, next) {
var store = self.stores[name];
//
// If the `store` doesn't have a `save` or saveSync`
// method(s), just ignore it and continue.
//
if (store.save) {
return store.save(value, function (err, data) {
if (err) {
return next(err);
}
if (typeof data == 'object' && data !== null) {
memo.push(data);
}
next(null, memo);
});
}
else if (store.saveSync) {
memo.push(store.saveSync());
}
next(null, memo);
}
//
// If we don't have a callback and the current
// store is capable of saving synchronously
// then do so.
//
if (!callback) {
return common.merge(names.reduce(saveStoreSync, []));
}
async.reduce(names, [], saveStore, function (err, objs) {
return err ? callback(err) : callback(null, common.merge(objs));
});
};
//
// ### @private function _execute (action, syncLength, [arguments])
// #### @action {string} Action to execute on `this.store`.
// #### @syncLength {number} Function length of the sync version.
// #### @arguments {Array} Arguments array to apply to the action
// Executes the specified `action` on all stores for this instance, ensuring a callback supplied
// to a synchronous store function is still invoked.
//
Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
var args = Array.prototype.slice.call(arguments, 2),
callback = typeof args[args.length - 1] === 'function' && args.pop(),
destructive = ['set', 'clear', 'merge', 'reset'].indexOf(action) !== -1,
self = this,
response,
mergeObjs = [],
keys = Object.keys(this.stores);
function runAction (name, next) {
var store = self.stores[name];
if (destructive && store.readOnly) {
return next();
}
return store[action].length > syncLength
? store[action].apply(store, args.concat(next))
: next(null, store[action].apply(store, args));
}
if (callback) {
return async.forEach(keys, runAction, function (err) {
return err ? callback(err) : callback();
});
}
keys.forEach(function (name) {
if (typeof response === 'undefined') {
var store = self.stores[name];
if (destructive && store.readOnly) {
return;
}
response = store[action].apply(store, args);
// Merge objects if necessary
if (response && action === 'get' && typeof response === 'object' && !Array.isArray(response)) {
mergeObjs.push(response);
response = undefined;
}
}
});
if (mergeObjs.length) {
response = common.merge(mergeObjs.reverse());
}
return response;
}
//
// Throw the `err` if a callback is not supplied
//
function onError(err, callback) {
if (callback) {
return callback(err);
}
throw err;
}

View File

@ -1,71 +0,0 @@
/*
* argv.js: Simple memory-based store for command-line arguments.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var util = require('util'),
Memory = require('./memory').Memory;
//
// ### function Argv (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Argv nconf store, a simple abstraction
// around the Memory store that can read command-line arguments.
//
var Argv = exports.Argv = function (options, usage) {
Memory.call(this, options);
this.type = 'argv';
this.readOnly = true;
this.options = options || false;
this.usage = usage;
};
// Inherit from the Memory store
util.inherits(Argv, Memory);
//
// ### function loadSync ()
// Loads the data passed in from `process.argv` into this instance.
//
Argv.prototype.loadSync = function () {
this.loadArgv();
return this.store;
};
//
// ### function loadArgv ()
// Loads the data passed in from the command-line arguments
// into this instance.
//
Argv.prototype.loadArgv = function () {
var self = this,
yargs, argv;
yargs = typeof this.options === 'object'
? require('yargs')(process.argv.slice(2)).options(this.options)
: require('yargs')(process.argv.slice(2));
if (typeof this.usage === 'string') { yargs.usage(this.usage) }
argv = yargs.argv
if (!argv) {
return;
}
this.readOnly = false;
Object.keys(argv).forEach(function (key) {
if (typeof argv[key] !== 'undefined') {
self.set(key, argv[key]);
}
});
this.showHelp = yargs.showHelp
this.help = yargs.help
this.readOnly = true;
return this.store;
};

View File

@ -1,91 +0,0 @@
/*
* env.js: Simple memory-based store for environment variables
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var util = require('util'),
common = require('../common'),
Memory = require('./memory').Memory;
//
// ### function Env (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Env nconf store, a simple abstraction
// around the Memory store that can read process environment variables.
//
var Env = exports.Env = function (options) {
Memory.call(this, options);
options = options || {};
this.type = 'env';
this.readOnly = true;
this.whitelist = options.whitelist || [];
this.separator = options.separator || '';
this.lowerCase = options.lowerCase || false;
if (({}).toString.call(options.match) === '[object RegExp]'
&& typeof options !== 'string') {
this.match = options.match;
}
if (options instanceof Array) {
this.whitelist = options;
}
if (typeof(options) === 'string') {
this.separator = options;
}
};
// Inherit from the Memory store
util.inherits(Env, Memory);
//
// ### function loadSync ()
// Loads the data passed in from `process.env` into this instance.
//
Env.prototype.loadSync = function () {
this.loadEnv();
return this.store;
};
//
// ### function loadEnv ()
// Loads the data passed in from `process.env` into this instance.
//
Env.prototype.loadEnv = function () {
var self = this;
var env = process.env;
if (this.lowerCase) {
Object.keys(env).forEach(function (key) {
env[key.toLowerCase()] = env[key];
});
}
this.readOnly = false;
Object.keys(env).filter(function (key) {
if (self.match && self.whitelist.length) {
return key.match(self.match) || self.whitelist.indexOf(key) !== -1
}
else if (self.match) {
return key.match(self.match);
}
else {
return !self.whitelist.length || self.whitelist.indexOf(key) !== -1
}
}).forEach(function (key) {
if (self.separator) {
self.set(common.key.apply(common, key.split(self.separator)), env[key]);
}
else {
self.set(key, env[key]);
}
});
this.readOnly = true;
return this.store;
};

View File

@ -1,313 +0,0 @@
/*
* file.js: Simple file storage engine for nconf files
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var crypto = require('crypto'),
fs = require('fs'),
path = require('path'),
util = require('util'),
formats = require('../formats'),
Memory = require('./memory').Memory,
exists = fs.exists || path.exists,
existsSync = fs.existsSync || path.existsSync;
//
// ### function File (options)
// #### @options {Object} Options for this instance
// Constructor function for the File nconf store, a simple abstraction
// around the Memory store that can persist configuration to disk.
//
var File = exports.File = function (options) {
if (!options || !options.file) {
throw new Error ('Missing required option `file`');
}
Memory.call(this, options);
this.type = 'file';
this.file = options.file;
this.dir = options.dir || process.cwd();
this.format = options.format || formats.json;
this.secure = options.secure;
this.spacing = options.json_spacing
|| options.spacing
|| 2;
if (this.secure) {
this.secure = typeof this.secure === 'string'
? { secret: this.secure }
: this.secure;
this.secure.alg = this.secure.alg || 'aes-256-ctr';
if (this.secure.secretPath) {
this.secure.secret = fs.readFileSync(this.secure.secretPath, 'utf8');
}
if (!this.secure.secret) {
throw new Error('secure.secret option is required');
}
}
if (options.search) {
this.search(this.dir);
}
};
// Inherit from the Memory store
util.inherits(File, Memory);
//
// ### function save (value, callback)
// #### @value {Object} _Ignored_ Left here for consistency
// #### @callback {function} Continuation to respond to when complete.
// Saves the current configuration object to disk at `this.file`
// using the format specified by `this.format`.
//
File.prototype.save = function (value, callback) {
if (!callback) {
callback = value;
value = null;
}
fs.writeFile(this.file, this.stringify(), callback);
};
//
// ### function saveSync (value, callback)
// #### @value {Object} _Ignored_ Left here for consistency
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Saves the current configuration object to disk at `this.file`
// using the format specified by `this.format` synchronously.
//
File.prototype.saveSync = function (value) {
fs.writeFileSync(this.file, this.stringify());
return this.store;
};
//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
// Responds with an Object representing all keys associated in this instance.
//
File.prototype.load = function (callback) {
var self = this;
exists(self.file, function (exists) {
if (!exists) {
return callback(null, {});
}
//
// Else, the path exists, read it from disk
//
fs.readFile(self.file, function (err, data) {
if (err) {
return callback(err);
}
try {
// Deals with string that include BOM
var stringData = data.toString();
if (stringData.charAt(0) === '\uFEFF') {
stringData = stringData.substr(1);
}
self.store = self.parse(stringData);
}
catch (ex) {
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message));
}
callback(null, self.store);
});
});
};
//
// ### function loadSync (callback)
// Attempts to load the data stored in `this.file` synchronously
// and responds appropriately.
//
File.prototype.loadSync = function () {
if (!existsSync(this.file)) {
this.store = {};
return this.store;
}
//
// Else, the path exists, read it from disk
//
try {
// Deals with file that include BOM
var fileData = fs.readFileSync(this.file, 'utf8');
if (fileData.charAt(0) === '\uFEFF') {
fileData = fileData.substr(1);
}
this.store = this.parse(fileData);
}
catch (ex) {
throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message);
}
return this.store;
};
//
// ### function stringify ()
// Returns an encrypted version of the contents IIF
// `this.secure` is enabled
//
File.prototype.stringify = function () {
var data = this.store,
self = this;
if (this.secure) {
data = Object.keys(data).reduce(function (acc, key) {
var value = self.format.stringify(data[key]);
acc[key] = {
alg: self.secure.alg,
value: cipherConvert(value, {
alg: self.secure.alg,
secret: self.secure.secret,
encs: { input: 'utf8', output: 'hex' }
})
}
return acc;
}, {});
}
return this.format.stringify(data, null, this.spacing);
};
//
// ### function parse (contents)
// Returns a decrypted version of the contents IFF
// `this.secure` is enabled.
//
File.prototype.parse = function (contents) {
var parsed = this.format.parse(contents),
self = this;
if (!this.secure) {
return parsed;
}
return Object.keys(parsed).reduce(function (acc, key) {
var decrypted = cipherConvert(parsed[key].value, {
alg: parsed[key].alg || self.secure.alg,
secret: self.secure.secret,
encs: { input: 'hex', output: 'utf8' }
});
acc[key] = self.format.parse(decrypted);
return acc;
}, {});
};
//
// ### function search (base)
// #### @base {string} Base directory (or file) to begin searching for the target file.
// Attempts to find `this.file` by iteratively searching up the
// directory structure
//
File.prototype.search = function (base) {
var looking = true,
fullpath,
previous,
stats;
base = base || process.cwd();
if (this.file[0] === '/') {
//
// If filename for this instance is a fully qualified path
// (i.e. it starts with a `'/'`) then check if it exists
//
try {
stats = fs.statSync(fs.realpathSync(this.file));
if (stats.isFile()) {
fullpath = this.file;
looking = false;
}
}
catch (ex) {
//
// Ignore errors
//
}
}
if (looking && base) {
//
// Attempt to stat the realpath located at `base`
// if the directory does not exist then return false.
//
try {
var stat = fs.statSync(fs.realpathSync(base));
looking = stat.isDirectory();
}
catch (ex) {
return false;
}
}
while (looking) {
//
// Iteratively look up the directory structure from `base`
//
try {
stats = fs.statSync(fs.realpathSync(fullpath = path.join(base, this.file)));
looking = stats.isDirectory();
}
catch (ex) {
previous = base;
base = path.dirname(base);
if (previous === base) {
//
// If we've reached the top of the directory structure then simply use
// the default file path.
//
try {
stats = fs.statSync(fs.realpathSync(fullpath = path.join(this.dir, this.file)));
if (stats.isDirectory()) {
fullpath = undefined;
}
}
catch (ex) {
//
// Ignore errors
//
}
looking = false;
}
}
}
//
// Set the file for this instance to the fullpath
// that we have found during the search. In the event that
// the search was unsuccessful use the original value for `this.file`.
//
this.file = fullpath || this.file;
return fullpath;
};
//
// ### function cipherConvert (contents, opts)
// Returns the result of the cipher operation
// on the contents contents.
//
function cipherConvert(contents, opts) {
var encs = opts.encs;
var cipher = crypto.createCipher(opts.alg, opts.secret);
return cipher.update(contents, encs.input, encs.output)
+ cipher.final(encs.output);
}

View File

@ -1,29 +0,0 @@
/*
* literal.js: Simple literal Object store for nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var util = require('util'),
Memory = require('./memory').Memory
var Literal = exports.Literal = function Literal (options) {
Memory.call(this, options);
options = options || {}
this.type = 'literal';
this.readOnly = true;
this.store = options.store || options;
};
// Inherit from Memory store.
util.inherits(Literal, Memory);
//
// ### function loadSync (callback)
// Returns the data stored in `this.store` synchronously.
//
Literal.prototype.loadSync = function () {
return this.store;
};

View File

@ -1,225 +0,0 @@
/*
* memory.js: Simple memory storage engine for nconf configuration(s)
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var common = require('../common');
//
// ### function Memory (options)
// #### @options {Object} Options for this instance
// Constructor function for the Memory nconf store which maintains
// a nested json structure based on key delimiters `:`.
//
// e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }`
//
var Memory = exports.Memory = function (options) {
options = options || {};
this.type = 'memory';
this.store = {};
this.mtimes = {};
this.readOnly = false;
this.loadFrom = options.loadFrom || null;
this.logicalSeparator = options.logicalSeparator || ':';
if (this.loadFrom) {
this.store = common.loadFilesSync(this.loadFrom);
}
};
//
// ### function get (key)
// #### @key {string} Key to retrieve for this instance.
// Retrieves the value for the specified key (if any).
//
Memory.prototype.get = function (key) {
var target = this.store,
path = common.path(key, this.logicalSeparator);
//
// Scope into the object to get the appropriate nested context
//
while (path.length > 0) {
key = path.shift();
if (target && target.hasOwnProperty(key)) {
target = target[key];
continue;
}
return undefined;
}
return target;
};
//
// ### function set (key, value)
// #### @key {string} Key to set in this instance
// #### @value {literal|Object} Value for the specified key
// Sets the `value` for the specified `key` in this instance.
//
Memory.prototype.set = function (key, value) {
if (this.readOnly) {
return false;
}
var target = this.store,
path = common.path(key, this.logicalSeparator);
if (path.length === 0) {
//
// Root must be an object
//
if (!value || typeof value !== 'object') {
return false;
}
else {
this.reset();
this.store = value;
return true;
}
}
//
// Update the `mtime` (modified time) of the key
//
this.mtimes[key] = Date.now();
//
// Scope into the object to get the appropriate nested context
//
while (path.length > 1) {
key = path.shift();
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}
target = target[key];
}
// Set the specified value in the nested JSON structure
key = path.shift();
target[key] = value;
return true;
};
//
// ### function clear (key)
// #### @key {string} Key to remove from this instance
// Removes the value for the specified `key` from this instance.
//
Memory.prototype.clear = function (key) {
if (this.readOnly) {
return false;
}
var target = this.store,
value = target,
path = common.path(key, this.logicalSeparator);
//
// Remove the key from the set of `mtimes` (modified times)
//
delete this.mtimes[key];
//
// Scope into the object to get the appropriate nested context
//
for (var i = 0; i < path.length - 1; i++) {
key = path[i];
value = target[key];
if (typeof value !== 'function' && typeof value !== 'object') {
return false;
}
target = value;
}
// Delete the key from the nested JSON structure
key = path[i];
delete target[key];
return true;
};
//
// ### function merge (key, value)
// #### @key {string} Key to merge the value into
// #### @value {literal|Object} Value to merge into the key
// Merges the properties in `value` into the existing object value
// at `key`. If the existing value `key` is not an Object, it will be
// completely overwritten.
//
Memory.prototype.merge = function (key, value) {
if (this.readOnly) {
return false;
}
//
// If the key is not an `Object` or is an `Array`,
// then simply set it. Merging is for Objects.
//
if (typeof value !== 'object' || Array.isArray(value) || value === null) {
return this.set(key, value);
}
var self = this,
target = this.store,
path = common.path(key, this.logicalSeparator),
fullKey = key;
//
// Update the `mtime` (modified time) of the key
//
this.mtimes[key] = Date.now();
//
// Scope into the object to get the appropriate nested context
//
while (path.length > 1) {
key = path.shift();
if (!target[key]) {
target[key] = {};
}
target = target[key];
}
// Set the specified value in the nested JSON structure
key = path.shift();
//
// If the current value at the key target is not an `Object`,
// or is an `Array` then simply override it because the new value
// is an Object.
//
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
target[key] = value;
return true;
}
return Object.keys(value).every(function (nested) {
return self.merge(common.keyed(self.logicalSeparator, fullKey, nested), value[nested]);
});
};
//
// ### function reset (callback)
// Clears all keys associated with this instance.
//
Memory.prototype.reset = function () {
if (this.readOnly) {
return false;
}
this.mtimes = {};
this.store = {};
return true;
};
//
// ### function loadSync
// Returns the store managed by this instance
//
Memory.prototype.loadSync = function () {
return this.store || {};
};

72
lib/stores/argv.mjs Normal file
View File

@ -0,0 +1,72 @@
import util from 'util'
import Memory from './memory.mjs'
import * as common from '../common.mjs'
//
// ### function Argv (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Argv nconf store, a simple abstraction
// around the Memory store that can read basic arguments.
//
const Argv = function(orgOpts) {
let options = orgOpts || {}
Memory.call(this, options)
this.readOnly = true
this.separator = options.separator || ''
this.lowerCase = options.lowerCase || false
this.parseValues = options.parseValues || false
this.transform = options.transform || false
this.prefix = options.prefix || '--'
this.useEqualsign = options.useEqualsign || false
if (!this.prefix) {
throw new Error('')
}
}
// Inherit from the Memory store
util.inherits(Argv, Memory)
//
// ### function load ()
// Loads the data passed in from `process.env` into this instance.
//
Argv.prototype.load = function () {
this.store = {}
let args = process.argv.slice(2)
this.readOnly = false
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith(this.prefix)) {
let key = args[i].slice(this.prefix.length)
if (this.lowerCase) {
key = key.toLowerCase()
}
if (this.separator) {
key = common.key(...key.split(this.separator))
}
if (this.useEqualsign) {
let equalSignIndex = key.indexOf('=')
if (equalSignIndex > 0) {
this.set(key.slice(0, equalSignIndex), key.slice(equalSignIndex + 1))
} else {
this.set(key, true)
}
} else if (args[i + 1] && !args[i + 1].startsWith(this.prefix)) {
this.set(key, args[i + 1])
i++
} else {
this.set(key, true)
}
}
}
this.readOnly = true
return this.store
}
export default Argv

102
lib/stores/env.mjs Normal file
View File

@ -0,0 +1,102 @@
import util from 'util'
import Memory from './memory.mjs'
import * as common from '../common.mjs'
//
// ### function Env (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Env nconf store, a simple abstraction
// around the Memory store that can read process environment variables.
//
const Env = function(orgOpts) {
let options = orgOpts || {}
if (Array.isArray(options)) {
options = { whitelist: options }
}
Memory.call(this, options)
this.readOnly = true
this.whitelist = options.whitelist || []
this.separator = options.separator || ''
this.lowerCase = options.lowerCase || false
this.parseValues = options.parseValues || false
this.transform = options.transform || false
if (!Array.isArray(this.whitelist)) {
throw new Error('Env parameter whitelist was not an array or contained non-string elements')
}
for (let i = 0; i < this.whitelist.length; i++) {
if (typeof(this.whitelist[i]) !== 'string') {
throw new Error('Env parameter whitelist was not an array or contained non-string elements')
}
this.whitelist[i] = this.whitelist[i].toLowerCase()
}
if (options.match) {
if (typeof(options.match) === 'string') {
options.match = new RegExp(options.match)
}
if (typeof(options.match.test) !== 'function') {
throw new Error('Env parameter match was not a valid RegExp')
}
this.match = options.match
}
}
// Inherit from the Memory store
util.inherits(Env, Memory)
//
// ### function load ()
// Loads the data passed in from `process.env` into this instance.
//
Env.prototype.load = function () {
let env = {}
if (this.lowerCase) {
Object.keys(process.env).forEach(function (key) {
env[key.toLowerCase()] = process.env[key]
})
} else {
env = process.env
}
if (this.transform) {
env = common.transform(env, this.transform)
}
this.readOnly = false
Object.keys(env).filter((key) => {
if (this.match && this.whitelist.length) {
return key.match(this.match) || this.whitelist.indexOf(key.toLowerCase()) !== -1
}
else if (this.match) {
return key.match(this.match)
}
else {
return !this.whitelist.length || this.whitelist.indexOf(key.toLowerCase()) !== -1
}
}).forEach((key) => {
var val = env[key]
if (this.parseValues) {
val = common.parseValues(val)
}
if (this.separator) {
this.set(common.key(...key.split(this.separator)), val)
}
else {
this.set(key, val)
}
})
this.readOnly = true
return this.store
}
export default Env

386
lib/stores/file.mjs Normal file
View File

@ -0,0 +1,386 @@
import fs from 'fs'
import path from 'path'
import util from 'util'
import crypto from 'crypto'
import Memory from './memory.mjs'
const fsPromise = fs.promises
//
// ### function File (options)
// #### @options {Object} Options for this instance
// Constructor function for the File nconf store, a simple abstraction
// around the Memory store that can persist configuration to disk.
//
const File = function (orgOpts) {
let options = orgOpts
if (typeof(options) === 'string') {
options = { file: options }
}
if (!options || !options.file) {
throw new Error('Missing required option `file`')
}
Memory.call(this, options)
this.file = options.file
// this.dir = options.dir || process.cwd()
this.format = options.format || JSON
this.secure = options.secure
// this.spacing = options.json_spacing || options.spacing || 2
if (this.secure) {
this.secure = typeof(this.secure === 'string')
? { secret: this.secure.toString() }
: this.secure
this.secure.alg = this.secure.alg || 'aes-256-ctr'
// if (this.secure.secretPath) {
// this.secure.secret = fs.readFileSync(this.secure.secretPath, 'utf8')
// }
if (!this.secure.secret) {
throw new Error('secure.secret option is required')
}
}
// if (options.search) {
// this.search(this.dir)
// }
}
// Inherit from the Memory store
util.inherits(File, Memory)
File.prototype.load = function () {
this.store = {}
let fileData
try {
fileData = fs.readFileSync(this.file, 'utf8')
}
catch (ex) {
throw new Error('Error opening ' + this.file + ': ' + ex.message)
}
// Deals with file that include BOM
if (fileData.charAt(0) === '\uFEFF') {
fileData = fileData.substr(1)
}
try {
this.store = this.parse(fileData)
}
catch (ex) {
throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message)
}
return this.store
}
File.prototype.loadAsync = function () {
this.store = {}
return fsPromise.readFile(this.file, 'utf8')
.then(fileData => {
let data = fileData
if (data.charAt(0) === '\uFEFF') {
data = data.substr(1)
}
try {
this.store = this.parse(data)
} catch (err) {
return Promise.reject(new Error("Error parsing your configuration file: [" + this.file + ']: ' + err.message))
}
return this.store
}, err => {
return Promise.reject(new Error('Error opening ' + this.file + ': ' + err.message))
})
}
//
// ### function parse (contents)
// Returns a decrypted version of the contents IFF
// `this.secure` is enabled.
//
File.prototype.parse = function (contents) {
let parsed = this.format.parse(contents)
if (this.secure) {
parsed = Object.keys(parsed).reduce((acc, key) => {
let value = parsed[key]
if (!value.iv) {
throw new Error(`Encrypted file ${this.file} is outdated (encrypted without iv). Please re-encrypt your file.`)
}
let decipher = crypto.createDecipheriv(value.alg, this.secure.secret, Buffer.from(value.iv, 'hex'))
let plaintext = decipher.update(value.value, 'hex', 'utf8')
plaintext += decipher.final('utf8')
acc[key] = this.format.parse(plaintext)
return acc
}, {})
}
return parsed
}
//
// ### function save (path)
// #### @path {string} The path to the file where we save the configuration to
// Saves the current configuration object to disk at `this.file`
//
File.prototype.save = function (orgPath) {
let path = orgPath
if (!path) {
path = this.file
}
fs.writeFileSync(path, this.stringify())
return this
}
//
// ### function save (path)
// #### @path {string} The path to the file where we save the configuration to
// Saves the current configuration object to disk at `this.file`
//
File.prototype.saveAsync = function(orgPath) {
let path = orgPath
if (!path) {
path = this.file
}
return fsPromise.writeFile(path, this.stringify()).then(() => {
return this
})
}
//
// ### function stringify ()
// Returns an encrypted version of the contents IIF
// `this.secure` is enabled
//
File.prototype.stringify = function () {
let data = this.store
if (this.secure) {
data = Object.keys(data).reduce((acc, key) => {
let value = this.format.stringify(data[key])
let iv = crypto.randomBytes(16)
let cipher = crypto.createCipheriv(this.secure.alg, this.secure.secret, iv)
let ciphertext = cipher.update(value, 'utf8', 'hex')
ciphertext += cipher.final('hex')
acc[key] = { alg: this.secure.alg, value: ciphertext, iv: iv.toString('hex') }
return acc
}, {})
}
return this.format.stringify(data, null, this.spacing)
}
/*
//
// ### function save (value, callback)
// #### @value {Object} _Ignored_ Left here for consistency
// #### @callback {function} Continuation to respond to when complete.
// Saves the current configuration object to disk at `this.file`
// using the format specified by `this.format`.
//
File.prototype.save = function (value, callback) {
this.saveToFile(this.file, value, callback)
}
//
// ### function saveToFile (path, value, callback)
// #### @path {string} The path to the file where we save the configuration to
// #### @format {Object} Optional formatter, default behing the one of the store
// #### @callback {function} Continuation to respond to when complete.
// Saves the current configuration object to disk at `this.file`
// using the format specified by `this.format`.
//
File.prototype.saveToFile = function (path, format, callback) {
if (!callback) {
callback = format
format = this.format
}
fs.writeFile(path, this.stringify(format), callback)
}
//
// ### function saveSync (value, callback)
// Saves the current configuration object to disk at `this.file`
// using the format specified by `this.format` synchronously.
//
File.prototype.saveSync = function () {
fs.writeFileSync(this.file, this.stringify())
return this.store
}
//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
// Responds with an Object representing all keys associated in this instance.
//
File.prototype.load = function (callback) {
let self = this
exists(self.file, function (exists) {
if (!exists) {
return callback(null, {})
}
//
// Else, the path exists, read it from disk
//
fs.readFile(self.file, function (err, data) {
if (err) {
return callback(err)
}
try {
// Deals with string that include BOM
let stringData = data.toString()
if (stringData.charAt(0) === '\uFEFF') {
stringData = stringData.substr(1)
}
self.store = self.parse(stringData)
}
catch (ex) {
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message))
}
callback(null, self.store)
})
})
}
//
// ### function loadSync (callback)
// Attempts to load the data stored in `this.file` synchronously
// and responds appropriately.
//
File.prototype.loadSync = function () {
if (!existsSync(this.file)) {
this.store = {}
return this.store
}
//
// Else, the path exists, read it from disk
//
try {
// Deals with file that include BOM
let fileData = fs.readFileSync(this.file, 'utf8')
if (fileData.charAt(0) === '\uFEFF') {
fileData = fileData.substr(1)
}
this.store = this.parse(fileData)
}
catch (ex) {
throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message)
}
return this.store
}
//
// ### function search (base)
// #### @base {string} Base directory (or file) to begin searching for the target file.
// Attempts to find `this.file` by iteratively searching up the
// directory structure
//
File.prototype.search = function (base) {
let looking = true,
fullpath,
previous,
stats
base = base || process.cwd()
if (this.file[0] === '/') {
//
// If filename for this instance is a fully qualified path
// (i.e. it starts with a `'/'`) then check if it exists
//
try {
stats = fs.statSync(fs.realpathSync(this.file))
if (stats.isFile()) {
fullpath = this.file
looking = false
}
}
catch (ex) {
//
// Ignore errors
//
}
}
if (looking && base) {
//
// Attempt to stat the realpath located at `base`
// if the directory does not exist then return false.
//
try {
let stat = fs.statSync(fs.realpathSync(base))
looking = stat.isDirectory()
}
catch (ex) {
return false
}
}
while (looking) {
//
// Iteratively look up the directory structure from `base`
//
try {
stats = fs.statSync(fs.realpathSync(fullpath = path.join(base, this.file)))
looking = stats.isDirectory()
}
catch (ex) {
previous = base
base = path.dirname(base)
if (previous === base) {
//
// If we've reached the top of the directory structure then simply use
// the default file path.
//
try {
stats = fs.statSync(fs.realpathSync(fullpath = path.join(this.dir, this.file)))
if (stats.isDirectory()) {
fullpath = undefined
}
}
catch (ex) {
//
// Ignore errors
//
}
looking = false
}
}
}
//
// Set the file for this instance to the fullpath
// that we have found during the search. In the event that
// the search was unsuccessful use the original value for `this.file`.
//
this.file = fullpath || this.file
return fullpath
}
*/
export default File

15
lib/stores/literal.mjs Normal file
View File

@ -0,0 +1,15 @@
import { inherits } from 'util'
import Memory from './memory.mjs'
function Literal (options) {
Memory.call(this, options)
this.type = 'literal'
this.readOnly = true
this.store = options
}
// Inherit from Memory store.
inherits(Literal, Memory)
export default Literal

212
lib/stores/memory.mjs Normal file
View File

@ -0,0 +1,212 @@
import * as common from '../common.mjs'
//
// ### function Memory (options)
// #### @options {Object} Options for this instance
// Constructor function for the Memory nconf store which maintains
// a nested json structure based on key delimiters `:`.
//
// e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }`
//
function Memory(orgOpts) {
let options = orgOpts || {}
this.type = 'memory'
this.store = {}
this.readOnly = options.readOnly || false
this.logicalSeparator = options.logicalSeparator || ':'
this.parseValues = options.parseValues || false
}
//
// ### function get (key)
// #### @key {string} Key to retrieve for this instance.
// Retrieves the value for the specified key (if any).
//
Memory.prototype.get = function (key) {
var target = this.store,
path = common.path(key, this.logicalSeparator)
//
// Scope into the object to get the appropriate nested context
//
while (path.length > 0) {
key = path.shift()
if (target && typeof target !== 'string' && target.hasOwnProperty(key)) {
target = target[key]
continue
}
return undefined
}
return target
}
//
// ### function set (key, value)
// #### @key {string} Key to set in this instance
// #### @value {literal|Object} Value for the specified key
// Sets the `value` for the specified `key` in this instance.
//
Memory.prototype.set = function (orgKey, orgValue) {
if (this.readOnly) {
return false
}
let key = orgKey
let value = orgValue
if (value === undefined && typeof(key) === 'object') {
key = null
value = orgKey
}
let target = this.store
let path = common.path(key, this.logicalSeparator)
if (path.length === 0) {
//
// Root must be an object
//
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
}
this.store = value
return true
}
key = path.shift()
//
// Scope into the object to get the appropriate nested context
//
while (path.length) {
if (!target[key]) {
target[key] = {}
}
target = target[key]
key = path.shift()
}
if (this.parseValues) {
value = common.parseValues(value)
}
if (value) {
if (Array.isArray(value)) {
value = common.mergeRecursiveArray(value)
} else if (typeof(value) === 'object') {
value = common.merge([value])
}
}
target[key] = value
return true
}
//
// ### function clear (key)
// #### @key {string} Key to remove from this instance
// Removes the value for the specified `key` from this instance.
//
Memory.prototype.clear = function (key) {
if (this.readOnly) {
return false
}
let target = this.store
let value = target
let path = common.path(key, this.logicalSeparator)
//
// Scope into the object to get the appropriate nested context
//
let i = 0
for (; i < path.length - 1; i++) {
key = path[i]
value = target[key]
if (typeof value !== 'function' && typeof value !== 'object') {
return false
}
target = value
}
// Delete the key from the nested JSON structure
key = path[i]
delete target[key]
return true
}
//
// ### function merge (key, value)
// #### @key {string} Key to merge the value into
// #### @value {literal|Object} Value to merge into the key
// Merges the properties in `value` into the existing object value
// at `key`.
//
Memory.prototype.merge = function (orgFullKey, orgValue) {
if (this.readOnly) {
return false
}
let fullKey = orgFullKey
let value = orgValue
// If fullkey is an object, do basic merge on root
if (typeof(fullKey) === 'object') {
this.store = common.merge(this.store, [fullKey])
return true
}
if (typeof(fullKey) === 'number') {
fullKey = fullKey.toString()
}
let target = this.store
let path = common.path(fullKey, this.logicalSeparator)
let key = path.shift()
//
// Scope into the object to get the appropriate nested context
//
while (path.length) {
if (!target[key]) {
target[key] = {}
}
target = target[key]
key = path.shift()
}
// Check if we actually need to do any merging. Sometimes a simple assign or "set"
// is all that is needed. This might be instances where the value is "null" (which
// would mean no merging is required) or if we're dealing with arrays on either side.
if (!value || typeof(value) !== 'object' || Array.isArray(value) || !target[key] || typeof(target[key]) !== 'object' || Array.isArray(target[key])) {
return this.set(fullKey, value)
}
target[key] = common.merge(target[key], [value])
return true
}
//
// ### function reset (callback)
// Clears all keys associated with this instance.
//
Memory.prototype.reset = function () {
if (this.readOnly) {
return false
}
this.store = {}
return true
}
//
// ### function loadSync
// Returns the store managed by this instance
//
Memory.prototype.loadSync = function () {
return this.store || {}
}
export default Memory

View File

@ -1,31 +1,42 @@
{
"name": "nconf",
"description": "Hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.",
"version": "0.8.2",
"author": "Charlie Robbins <charlie.robbins@gmail.com>",
"name": "nconf-lite",
"description": "Zero dependency hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.",
"version": "2.1.0",
"author": "Jonatan Nilsson <jonatan@nilsson.is>",
"repository": {
"type": "git",
"url": "http://github.com/flatiron/nconf.git"
"url": "http://github.com/nfp-projects/nconf-lite.git"
},
"keywords": [
"configuration",
"key value store",
"plugabble"
],
"dependencies": {
"async": "^1.4.0",
"ini": "^1.3.0",
"yargs": "^3.19.0"
"watch": {
"test": {
"patterns": [
"{lib,test}/*"
],
"extensions": "js,mjs",
"quiet": true,
"inherit": true
}
},
"dependencies": {},
"devDependencies": {
"vows": "0.8.x"
"eltro": "^1.0.2"
},
"main": "./lib/nconf",
"main": "./lib/nconf.mjs",
"scripts": {
"test": "vows test/*-test.js test/**/*-test.js --spec"
"test": "eltro test/**/*.test.mjs -r dot",
"test:watch": "npm-watch test",
"lint": "eslint ."
},
"files": [
"lib"
],
"engines": {
"node": ">= 0.4.0"
"node": ">= 13.0.0"
},
"license": "MIT"
}

View File

@ -1,32 +0,0 @@
/*
* common.js: Tests for common utility function in nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
path = require('path'),
vows = require('vows'),
assert = require('assert'),
helpers = require('./helpers'),
nconf = require('../lib/nconf');
var mergeDir = path.join(__dirname, 'fixtures', 'merge'),
files = fs.readdirSync(mergeDir).map(function (f) { return path.join(mergeDir, f) });
vows.describe('nconf/common').addBatch({
"Using nconf.common module": {
"the loadFiles() method": {
topic: function () {
nconf.loadFiles(files, this.callback);
},
"should merge the files correctly": helpers.assertMerged
},
"the loadFilesSync() method": {
"should merge the files correctly": function () {
helpers.assertMerged(null, nconf.loadFilesSync(files));
}
}
}
}).export(module);

258
test/common.test.mjs Normal file
View File

@ -0,0 +1,258 @@
import { Eltro as t, assert} from 'eltro'
import * as common from '../lib/common.mjs'
t.describe('#validkeyvalue', function() {
t.test('should return key if valid key', function() {
assert.strictEqual(common.validkeyvalue('asdf'), null)
assert.strictEqual(common.validkeyvalue(''), null)
assert.strictEqual(common.validkeyvalue(), null)
assert.strictEqual(common.validkeyvalue(null), null)
assert.strictEqual(common.validkeyvalue(undefined), null)
})
t.test('should return invalid valuetype in result', function() {
assert.strictEqual(common.validkeyvalue([]), '__invalid_valuetype_of_object__')
assert.strictEqual(common.validkeyvalue({}), '__invalid_valuetype_of_object__')
assert.strictEqual(common.validkeyvalue([]), '__invalid_valuetype_of_object__')
assert.strictEqual(common.validkeyvalue({}), '__invalid_valuetype_of_object__')
assert.strictEqual(common.validkeyvalue(() => {}), '__invalid_valuetype_of_function__')
assert.strictEqual(common.validkeyvalue(function() {}), '__invalid_valuetype_of_function__')
})
})
t.describe('#path()', function() {
t.test('it should support normal operation', function() {
assert.deepStrictEqual(common.path('a:b:c'), ['a','b','c'])
assert.deepStrictEqual(common.path('a'), ['a'])
})
t.test('it should support different separator', function() {
assert.deepStrictEqual(common.path('a:b:c', '__'), ['a:b:c'])
assert.deepStrictEqual(common.path('a__b__c', '__'), ['a','b','c'])
assert.deepStrictEqual(common.path('a', '__'), ['a'])
})
t.test('it should work with non-string keys', function() {
assert.deepStrictEqual(common.path(1, '__'), ['1'])
assert.deepStrictEqual(common.path(4.3, '__'), ['4.3'])
})
t.test('it should return invalid value on non-supported keys', function() {
assert.deepStrictEqual(common.path([], '__'), ['__invalid_valuetype_of_object__'])
assert.strictEqual(common.path([], '__').length, 1)
assert.deepStrictEqual(common.path({}, '__'), ['__invalid_valuetype_of_object__'])
assert.strictEqual(common.path({}, '__').length, 1)
assert.deepStrictEqual(common.path([]), ['__invalid_valuetype_of_object__'])
assert.strictEqual(common.path([]).length, 1)
assert.deepStrictEqual(common.path({}), ['__invalid_valuetype_of_object__'])
assert.strictEqual(common.path({}).length, 1)
assert.deepStrictEqual(common.path(() => {}), ['__invalid_valuetype_of_function__'])
assert.strictEqual(common.path(() => {}).length, 1)
assert.deepStrictEqual(common.path(function() {}), ['__invalid_valuetype_of_function__'])
assert.strictEqual(common.path(function() {}).length, 1)
})
t.test('it should support empty values and return empty path', function() {
assert.deepStrictEqual(common.path(null, '__'), [])
assert.strictEqual(common.path(null, '__').length, 0)
assert.deepStrictEqual(common.path(undefined, '__'), [])
assert.strictEqual(common.path(undefined, '__').length, 0)
assert.deepStrictEqual(common.path('', '__'), [])
assert.strictEqual(common.path('', '__').length, 0)
assert.deepStrictEqual(common.path(null), [])
assert.strictEqual(common.path(null).length, 0)
assert.deepStrictEqual(common.path(undefined), [])
assert.strictEqual(common.path(undefined).length, 0)
assert.deepStrictEqual(common.path(''), [])
assert.strictEqual(common.path('').length, 0)
assert.deepStrictEqual(common.path(), [])
assert.strictEqual(common.path().length, 0)
})
})
t.describe('#key()', function() {
t.test('it should work with common values', function() {
assert.strictEqual(common.key('a'), 'a')
assert.strictEqual(common.key('a', 'b'), 'a:b')
assert.strictEqual(common.key('a', 'b', 'c'), 'a:b:c')
assert.strictEqual(common.key(123), '123')
assert.strictEqual(common.key(5.4), '5.4')
assert.strictEqual(common.key('a', 123, 'b'), 'a:123:b')
assert.strictEqual(common.key('a', 5.4, 'b'), 'a:5.4:b')
assert.strictEqual(common.key('a', 123, 456), 'a:123:456')
assert.strictEqual(common.key('a', 5.4, 456), 'a:5.4:456')
})
t.test('it should text replace invalid keys with the invalid value string', function() {
assert.strictEqual(common.key([]), '__invalid_valuetype_of_object__')
assert.strictEqual(common.key({}), '__invalid_valuetype_of_object__')
assert.strictEqual(common.key([]), '__invalid_valuetype_of_object__')
assert.strictEqual(common.key({}), '__invalid_valuetype_of_object__')
assert.strictEqual(common.key(() => {}), '__invalid_valuetype_of_function__')
assert.strictEqual(common.key(function() {}), '__invalid_valuetype_of_function__')
assert.strictEqual(common.key('a', [], 'b'), 'a:__invalid_valuetype_of_object__:b')
assert.strictEqual(common.key('a', {}, 'b'), 'a:__invalid_valuetype_of_object__:b')
assert.strictEqual(common.key('a', [], 'b'), 'a:__invalid_valuetype_of_object__:b')
assert.strictEqual(common.key('a', {}, 'b'), 'a:__invalid_valuetype_of_object__:b')
assert.strictEqual(common.key('a', () => {}, 'b'), 'a:__invalid_valuetype_of_function__:b')
assert.strictEqual(common.key('a', function() {}, 'b'), 'a:__invalid_valuetype_of_function__:b')
})
})
t.describe('#keyed()', function() {
t.test('it should work with common values', function() {
assert.strictEqual(common.keyed('__', 'a'), 'a')
assert.strictEqual(common.keyed('__', 'a', 'b'), 'a__b')
assert.strictEqual(common.keyed('__', 'a', 'b', 'c'), 'a__b__c')
assert.strictEqual(common.keyed('__', 123), '123')
assert.strictEqual(common.keyed('__', 5.4), '5.4')
assert.strictEqual(common.keyed('__', 'a', 123, 'b'), 'a__123__b')
assert.strictEqual(common.keyed('__', 'a', 5.4, 'b'), 'a__5.4__b')
assert.strictEqual(common.keyed('__', 'a', 123, 456), 'a__123__456')
assert.strictEqual(common.keyed('__', 'a', 5.4, 456), 'a__5.4__456')
})
t.test('it should text replace invalid keys with the invalid value string', function() {
assert.strictEqual(common.keyed('__', []), '__invalid_valuetype_of_object__')
assert.strictEqual(common.keyed('__', {}), '__invalid_valuetype_of_object__')
assert.strictEqual(common.keyed('__', []), '__invalid_valuetype_of_object__')
assert.strictEqual(common.keyed('__', {}), '__invalid_valuetype_of_object__')
assert.strictEqual(common.keyed('__', () => {}), '__invalid_valuetype_of_function__')
assert.strictEqual(common.keyed('__', function() {}), '__invalid_valuetype_of_function__')
assert.strictEqual(common.keyed('__', 'a', [], 'b'), 'a____invalid_valuetype_of_object____b')
assert.strictEqual(common.keyed('__', 'a', {}, 'b'), 'a____invalid_valuetype_of_object____b')
assert.strictEqual(common.keyed('__', 'a', [], 'b'), 'a____invalid_valuetype_of_object____b')
assert.strictEqual(common.keyed('__', 'a', {}, 'b'), 'a____invalid_valuetype_of_object____b')
assert.strictEqual(common.keyed('__', 'a', () => {}, 'b'), 'a____invalid_valuetype_of_function____b')
assert.strictEqual(common.keyed('__', 'a', function() {}, 'b'), 'a____invalid_valuetype_of_function____b')
})
})
t.describe('#merge()', function() {
t.test('should throw if not sent an array', function() {
assert.throws(function() { common.merge({}) })
assert.throws(function() { common.merge('asdf') })
assert.throws(function() { common.merge(12412) })
assert.throws(function() { common.merge(null) })
assert.throws(function() { common.merge(undefined) })
assert.throws(function() { common.merge() })
})
t.test('it should be able to merge properly', function() {
// Test individual check and then re-check that the original are untouched
let fn = function() { return true }
let a = null
let b = null
let c = null
a = { a: 1 }
b = { b: 2 }
assert.deepStrictEqual(common.merge([a, b]), { a: 1, b: 2 })
assert.deepStrictEqual(a, { a: 1 })
assert.deepStrictEqual(b, { b: 2 })
a = { a: 1 }
b = { b: 2 }
c = { a: 3 }
assert.deepStrictEqual(common.merge([a, b, c]), { a: 3, b: 2 })
assert.deepStrictEqual(a, { a: 1 })
assert.deepStrictEqual(b, { b: 2 })
assert.deepStrictEqual(c, { a: 3 })
a = { a: [1, 2] }
b = { a: [2, 3] }
assert.deepStrictEqual(common.merge([a, b]), { a: [2, 3] })
assert.deepStrictEqual(b, { a: [2, 3] })
assert.deepStrictEqual(a, { a: [1, 2] })
a = { a: [1, 2] }
b = { a: [2, [3, 4]] }
assert.deepStrictEqual(common.merge([a, b]), { a: [2, [3, 4]] })
assert.deepStrictEqual(a, { a: [1, 2] })
assert.deepStrictEqual(b, { a: [2, [3, 4]] })
a = { a: fn }
b = { b: 2 }
assert.deepStrictEqual(common.merge([a, b]), { a: fn, b: 2 })
assert.deepStrictEqual(a, { a: fn })
assert.deepStrictEqual(b, { b: 2 })
a = { a: fn }
b = { b: 2 }
c = { a: 3 }
assert.deepStrictEqual(common.merge([a, b, c]), { a: 3, b: 2 })
assert.deepStrictEqual(a, { a: fn })
assert.deepStrictEqual(b, { b: 2 })
assert.deepStrictEqual(c, { a: 3 })
a = { apples: true, bananas: true, foo: { bar: "boo" }, candy: { something: "file1", something1: true, something2: true, something5: { first: 1, second: 2 } }, unicorn: { exists: true }}
b = { candy: { something: "file2", something3: true, something4: true }, dates: true, elderberries: true, unicorn: null }
assert.deepStrictEqual(common.merge([a, b]), { apples: true, bananas: true, foo: { bar: "boo" }, candy: { something: "file2", something1: true, something2: true, something3: true, something4: true, something5: { first: 1, second: 2 } }, dates: true, elderberries: true, unicorn: null })
assert.deepStrictEqual(a, { apples: true, bananas: true, foo: { bar: "boo" }, candy: { something: "file1", something1: true, something2: true, something5: { first: 1, second: 2 } }, unicorn: { exists: true }})
assert.deepStrictEqual(b, { candy: { something: "file2", something3: true, something4: true }, dates: true, elderberries: true, unicorn: null })
// weird behavior from old merge but I have no better idea to turn arrays
// into object so this is "good enough" for now
a = { a: 1 }
b = { a: 2 }
c = ['test']
assert.deepStrictEqual(common.merge([a, b, c]), { '0': 'test', a: 2 })
assert.deepStrictEqual(a, { a: 1 })
assert.deepStrictEqual(b, { a: 2 })
assert.deepStrictEqual(c, ['test'])
})
t.test('it should support edge cases properly', function() {
let a = { a: { b: 1 } }
let b = { a: ['test'] }
let out = common.merge([a, b])
assert.deepStrictEqual(out, { a: ['test'] })
b = { a: { b: 1 } }
out = common.merge(out, [b])
assert.deepStrictEqual(out, { a: { b: 1 } })
})
})
t.describe('#capitalize()', function() {
t.test('should return original if not string', function() {
const assertObject = {}
const assertArray = []
assert.strictEqual(common.capitalize(assertObject), assertObject)
assert.strictEqual(common.capitalize(assertArray), assertArray)
assert.strictEqual(common.capitalize(null), null)
assert.strictEqual(common.capitalize(undefined), undefined)
assert.strictEqual(common.capitalize(), undefined)
})
t.test('should adapt value to string if value type', function() {
assert.strictEqual(common.capitalize(12412), '12412')
assert.strictEqual(common.capitalize(123.4), '123.4')
})
t.test('should otherwise capitalize', function() {
assert.strictEqual(common.capitalize('asdf'), 'Asdf')
assert.strictEqual(common.capitalize('test test'), 'Test test')
assert.strictEqual(common.capitalize('FOO'), 'FOO')
assert.strictEqual(common.capitalize('f'), 'F')
assert.strictEqual(common.capitalize('F'), 'F')
assert.strictEqual(common.capitalize(''), '')
})
})
t.describe('#parseValues()', function() {
t.test('should special handle undefined', function() {
assert.strictEqual(common.parseValues('undefined'), undefined)
})
t.test('should normally json parse string', function() {
assert.strictEqual(common.parseValues('null'), null)
assert.deepStrictEqual(common.parseValues('{"a": 1}'), { a: 1 })
assert.deepStrictEqual(common.parseValues('["a", 1]'), [ 'a', 1 ])
assert.strictEqual(common.parseValues('123'), 123)
assert.strictEqual(common.parseValues('"{\\"a\\": 1}"'), '{"a": 1}')
})
t.test('should otherwise return original string if errors are found', function() {
assert.strictEqual(common.parseValues('anull'), 'anull')
assert.deepStrictEqual(common.parseValues('a{"a": 1}'), 'a{"a": 1}')
assert.deepStrictEqual(common.parseValues('a["a", 1]'), 'a["a", 1]')
assert.strictEqual(common.parseValues('a123'), 'a123')
assert.strictEqual(common.parseValues('a"{\\"a\\": 1}"'), 'a"{\\"a\\": 1}"')
})
})

View File

@ -1,159 +0,0 @@
/*
* complete-test.js: Complete test for multiple stores.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
path = require('path'),
vows = require('vows'),
assert = require('assert'),
nconf = require('../lib/nconf'),
data = require('./fixtures/data').data,
helpers = require('./helpers');
var completeTest = helpers.fixture('complete-test.json'),
complete = helpers.fixture('complete.json');
// prime the process.env
process.env['NCONF_foo'] = 'bar';
process.env.FOO = 'bar';
process.env.BAR = 'zalgo';
process.env.NODE_ENV = 'debug';
process.env.FOOBAR = 'should not load';
vows.describe('nconf/multiple-stores').addBatch({
"When using the nconf with multiple providers": {
topic: function () {
var that = this;
helpers.cp(complete, completeTest, function () {
nconf.env({
// separator: '__',
match: /^NCONF_/,
whitelist: ['NODE_ENV', 'FOO', 'BAR']
});
nconf.file({ file: completeTest });
nconf.use('argv', { type: 'literal', store: data });
that.callback();
});
},
"should have the correct `stores`": function () {
assert.isObject(nconf.stores.env);
assert.isObject(nconf.stores.argv);
assert.isObject(nconf.stores.file);
},
"env vars": {
"are present": function () {
['NODE_ENV', 'FOO', 'BAR', 'NCONF_foo'].forEach(function (key) {
assert.equal(nconf.get(key), process.env[key]);
});
}
},
"json vars": {
topic: function () {
fs.readFile(complete, 'utf8', this.callback);
},
"are present": function (err, data) {
assert.isNull(err);
data = JSON.parse(data);
Object.keys(data).forEach(function (key) {
assert.deepEqual(nconf.get(key), data[key]);
});
}
},
"literal vars": {
"are present": function () {
Object.keys(data).forEach(function (key) {
assert.deepEqual(nconf.get(key), data[key]);
});
}
},
"and saving *synchronously*": {
topic: function () {
nconf.set('weebls', 'stuff');
return nconf.save();
},
"correct return value": function (topic) {
Object.keys(topic).forEach(function (key) {
assert.deepEqual(topic[key], nconf.get(key));
});
},
"the file": {
topic: function () {
fs.readFile(completeTest, 'utf8', this.callback);
},
"saved correctly": function (err, data) {
data = JSON.parse(data);
Object.keys(data).forEach(function (key) {
assert.deepEqual(data[key], nconf.get(key));
});
assert.equal(nconf.get('weebls'), 'stuff');
}
}
},
teardown: function () {
// remove the file so that we can test saving it async
fs.unlinkSync(completeTest);
}
}
}).addBatch({
// Threw this in it's own batch to make sure it's run separately from the
// sync check
"When using the nconf with multiple providers": {
"and saving *asynchronously*": {
topic: function () {
nconf.set('weebls', 'crap');
nconf.save(this.callback);
},
"correct return value": function (err, data) {
assert.isNull(err);
Object.keys(data).forEach(function (key) {
assert.deepEqual(data[key], nconf.get(key));
});
},
"the file": {
topic: function () {
fs.readFile(completeTest, 'utf8', this.callback);
},
"saved correctly": function (err, data) {
assert.isNull(err);
data = JSON.parse(data);
Object.keys(data).forEach(function (key) {
assert.deepEqual(nconf.get(key), data[key]);
});
assert.equal(nconf.get('weebls'), 'crap');
}
}
},
teardown: function () {
fs.unlinkSync(completeTest);
nconf.remove('file');
nconf.remove('memory');
nconf.remove('argv');
nconf.remove('env');
}
}
}).addBatch({
// Threw this in it's own batch to make sure it's run separately from the
// sync check
"When using env with lowerCase:true": {
topic: function () {
var that = this;
helpers.cp(complete, completeTest, function () {
nconf.env({ lowerCase: true });
that.callback();
});
},
"env vars": {
"keys also available as lower case": function () {
Object.keys(process.env).forEach(function (key) {
assert.equal(nconf.get(key.toLowerCase()), process.env[key]);
});
}
},
teardown: function () {
nconf.remove('env');
}
}
}).export(module);

View File

@ -1,11 +1,4 @@
/*
* data.js: Simple data fixture for configuration test.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
exports.data = {
export const data = {
isNull: null,
literal: 'bazz',
arr: ['one', 2, true, { value: 'foo' }],
@ -20,7 +13,7 @@ exports.data = {
}
};
exports.merge = {
export const merge = {
prop1: 1,
prop2: [1, 2, 3],
prop3: {

View File

@ -1,3 +0,0 @@
{
"test": "empty"
}

9
test/fixtures/merge/file3.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"candy": {
"something": "much better something for you",
"something18": "completely unique",
"something5": {
"second": 99
}
}
}

View File

@ -1,10 +0,0 @@
/*
* default-argv.js: Test fixture for using yargs defaults with nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var nconf = require('../../../lib/nconf').argv().env();
process.stdout.write(nconf.get('something'));

View File

@ -1,16 +0,0 @@
/*
* nconf-change-argv.js: Test fixture for changing argv on the fly
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var nconf = require('../../../lib/nconf').argv();
//
// Remove 'badValue', 'evenWorse' and 'OHNOEZ'
//
process.argv.splice(3, 3);
nconf.stores['argv'].loadArgv();
process.stdout.write(nconf.get('something'));

View File

@ -1,10 +0,0 @@
/*
* nconf-env.js: Test fixture for using process.env defaults with nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var nconf = require('../../../lib/nconf').env();
process.stdout.write(nconf.get('SOMETHING'));

View File

@ -1,17 +0,0 @@
/*
* nconf-hierarchical-file-argv.js: Test fixture for using yargs defaults and a file store with nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
* (C) 2011, Sander Tolsma
*
*/
var path = require('path'),
nconf = require('../../../lib/nconf');
nconf.argv();
nconf.add('file', {
file: path.join(__dirname, '../hierarchy/hierarchical.json')
});
process.stdout.write(nconf.get('something') || 'undefined');

View File

@ -1,18 +0,0 @@
/*
* nconf-hierarchical-load-merge.js: Test fixture for loading and merging nested objects across stores.
*
* (C) 2012, Charlie Robbins and the Contributors.
* (C) 2012, Michael Hart
*
*/
var path = require('path'),
nconf = require('../../../lib/nconf');
nconf.argv()
.file(path.join(__dirname, '..', 'merge', 'file1.json'));
process.stdout.write(JSON.stringify({
apples: nconf.get('apples'),
candy: nconf.get('candy')
}));

View File

@ -1,32 +0,0 @@
/*
* nconf-hierarchical-load-save.js: Test fixture for using yargs, envvars and a file store with nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
path = require('path'),
nconf = require('../../../lib/nconf');
//
// Setup nconf to use (in-order):
// 1. Command-line arguments
// 2. Environment variables
// 3. A file located at 'path/to/config.json'
//
nconf.argv()
.env()
.file({ file: path.join(__dirname, '..', 'load-save.json') });
//
// Set a few variables on `nconf`.
//
nconf.set('database:host', '127.0.0.1');
nconf.set('database:port', 5984);
process.stdout.write(nconf.get('foo'));
//
// Save the configuration object to disk
//
nconf.save();

View File

@ -1,11 +0,0 @@
/*
* nconf-nested-env.js: Test fixture for env with nested keys.
*
* (C) 2012, Charlie Robbins and the Contributors.
* (C) 2012, Michael Hart
*
*/
var nconf = require('../../../lib/nconf').env('_');
process.stdout.write(nconf.get('SOME:THING'));

View File

@ -1,12 +0,0 @@
/*
* provider-argv.js: Test fixture for using yargs defaults with nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var nconf = require('../../../lib/nconf');
var provider = new (nconf.Provider)().argv();
process.stdout.write(provider.get('something'));

View File

@ -1,12 +0,0 @@
/*
* provider-argv.js: Test fixture for using process.env defaults with nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var nconf = require('../../../lib/nconf');
var provider = new (nconf.Provider)().env();
process.stdout.write(provider.get('SOMETHING'));

22
test/fixtures/secure-iv.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"isNull": {
"alg": "aes-256-ctr",
"value": "16f39325",
"iv": "49e7803a2a5ef98c7a51a8902b76dd10"
},
"literal": {
"alg": "aes-256-ctr",
"value": "647965c22605",
"iv": "b654e01aed262f37d0acf200be193985"
},
"arr": {
"alg": "aes-256-ctr",
"value": "70c6d3b2ec3820e4d4fa3d7834fcf7fd51190a9e580944583ca7b30f42d230b7f271e72287f6c2ab4624685c779936aa2ed19b90",
"iv": "b5263665331158c2165fe623a895870f"
},
"obj": {
"alg": "aes-256-ctr",
"value": "521f1995698896d2fe7d76d16bec9e86b9b5b25bcfae3a4f3c52ad6f0425c3fd059793b9dec7927c616f4c4c57f2b9ee833f8c865d9aa42bbb37203763703755a0c79afd13f8c0e96e9ac47f490a12105583e31ff628104e6b216265b849cdf6642eb8a4b5bd6f532339a5f5737bd6a7ae6e7bb22148cbcfa549802336cb1322fb701151b25b5863c5c6d5047875aed9806350ae0e292c259e82d5a561eb672402d20230a0c3107ff2b1e6259ccb1bbbe7a25ce965ce56cb32f679",
"iv": "355f307363d69ed58345ae395744f5f0"
}
}

28
test/fixtures/store.json vendored Normal file
View File

@ -0,0 +1,28 @@
{
"isNull": null,
"literal": "bazz",
"arr": [
"one",
2,
true,
{
"value": "foo"
}
],
"obj": {
"host": "localhost",
"port": 5984,
"array": [
"one",
2,
true,
{
"foo": "bar"
}
],
"auth": {
"username": "admin",
"password": "password"
}
}
}

View File

@ -1,68 +0,0 @@
/*
* helpers.js: Test helpers for nconf.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var assert = require('assert'),
spawn = require('child_process').spawn,
util = require('util'),
fs = require('fs'),
path = require('path'),
nconf = require('../lib/nconf');
exports.assertMerged = function (err, merged) {
merged = merged instanceof nconf.Provider
? merged.store.store
: merged;
assert.isNull(err);
assert.isObject(merged);
assert.isTrue(merged.apples);
assert.isTrue(merged.bananas);
assert.isObject(merged.candy);
assert.isTrue(merged.candy.something1);
assert.isTrue(merged.candy.something2);
assert.isTrue(merged.candy.something3);
assert.isTrue(merged.candy.something4);
assert.isTrue(merged.dates);
assert.isTrue(merged.elderberries);
};
exports.assertSystemConf = function (options) {
return {
topic: function () {
var env = null;
if (options.env) {
env = {}
Object.keys(process.env).forEach(function (key) {
env[key] = process.env[key];
});
Object.keys(options.env).forEach(function (key) {
env[key] = options.env[key];
});
}
var child = spawn('node', [options.script].concat(options.argv), { env: env });
child.stdout.once('data', this.callback.bind(this, null));
},
"should respond with the value passed into the script": function (_, data) {
assert.equal(data.toString(), 'foobar');
}
}
}
// copy a file
exports.cp = function (from, to, callback) {
fs.readFile(from, function (err, data) {
if (err) return callback(err);
fs.writeFile(to, data, callback);
});
};
exports.fixture = function (file) {
return path.join(__dirname, 'fixtures', file);
};

64
test/helpers.mjs Normal file
View File

@ -0,0 +1,64 @@
import { assert } from 'eltro'
import fs from 'fs'
import path from 'path'
import { exec as ex } from 'child_process'
import { fileURLToPath } from 'url'
import Nconf from '../lib/nconf.mjs'
let __dirname = path.dirname(fileURLToPath(import.meta.url))
export function assertMerged(err, merged) {
merged = merged instanceof Nconf
? merged.store.store
: merged;
assert.strictEqual(err, null)
assert.strictEqual(typeof(merged), 'object')
assert.ok(merged.apples)
assert.ok(merged.bananas)
assert.strictEqual(typeof(merged.candy), 'object')
assert.ok(merged.candy.something1)
assert.ok(merged.candy.something2)
assert.ok(merged.candy.something3)
assert.ok(merged.candy.something4)
assert.ok(merged.dates)
assert.ok(merged.elderberries)
};
// copy a file
export function cp(from, to) {
return new Promise(function(res, rej) {
fs.readFile(from, function (err, data) {
if (err) return rej(err);
fs.writeFile(to, data, function(err, data) {
if (err) return rej(err)
res(data)
});
});
})
};
/*export function exec(script, prefix = 'node') {
let command = `${prefix} ${script}`
return new Promise(function(res, rej) {
ex(command,
function (err, stdout, stderr) {
if (err) {
err.stdout = stdout
err.stderr = stderr
return rej(err)
}
res({
stdout,
stderr,
})
}
)
})
}*/
export function fixture(file) {
return path.resolve(path.join(__dirname, 'fixtures', file));
};

View File

@ -1,113 +0,0 @@
/*
* hierarchy-test.js: Basic tests for hierarchical file stores.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
spawn = require('child_process').spawn,
vows = require('vows'),
nconf = require('../lib/nconf');
var configDir = path.join(__dirname, 'fixtures', 'hierarchy'),
globalConfig = path.join(configDir, 'global.json'),
userConfig = path.join(configDir, 'user.json');
vows.describe('nconf/hierarchy').addBatch({
"When using nconf": {
"configured with two file stores": {
topic: function () {
nconf.add('user', { type: 'file', file: userConfig });
nconf.add('global', { type: 'file', file: globalConfig });
nconf.load();
return nconf;
},
"should have the appropriate keys present": function () {
assert.equal(nconf.get('title'), 'My specific title');
assert.equal(nconf.get('color'), 'green');
assert.equal(nconf.get('movie'), 'Kill Bill');
}
},
"configured with two file stores using `file`": {
topic: function () {
nconf.file('user', userConfig);
nconf.file('global', globalConfig);
nconf.load();
return nconf;
},
"should have the appropriate keys present": function () {
assert.equal(nconf.get('title'), 'My specific title');
assert.equal(nconf.get('color'), 'green');
assert.equal(nconf.get('movie'), 'Kill Bill');
}
},
"configured with .argv(), .env() and .file()": {
topic: function () {
var configFile = path.join(__dirname, 'fixtures', 'load-save.json'),
script = path.join(__dirname, 'fixtures', 'scripts', 'nconf-hierarchical-load-save.js'),
argv = ['--foo', 'foo', '--bar', 'bar'],
that = this,
data = '',
child;
try { fs.unlinkSync(configFile) }
catch (ex) { }
child = spawn('node', [script].concat(argv));
child.stdout.on('data', function (d) {
data += d;
});
child.on('close', function () {
fs.readFile(configFile, 'utf8', that.callback.bind(null, null, data));
});
},
"should not persist information passed in to process.env and process.argv to disk ": function (_, data, _, ondisk){
assert.equal(data, 'foo');
assert.deepEqual(JSON.parse(ondisk), {
database: {
host: '127.0.0.1',
port: 5984
}
});
}
},
"configured with .argv(), .file() and invoked with nested command line options": {
topic: function () {
var script = path.join(__dirname, 'fixtures', 'scripts', 'nconf-hierarchical-load-merge.js'),
argv = ['--candy:something', 'foo', '--candy:something5:second', 'bar'],
that = this,
data = '',
child;
child = spawn('node', [script].concat(argv));
child.stdout.on('data', function (d) {
data += d;
});
child.on('close', function() {
that.callback(null, data);
});
},
"should merge nested objects ": function (err, data) {
assert.deepEqual(JSON.parse(data), {
apples: true,
candy: {
something: 'foo',
something1: true,
something2: true,
something5: {
first: 1,
second: 'bar'
}
}
});
}
}
}
}).export(module);

56
test/hierarchy.test.mjs Normal file
View File

@ -0,0 +1,56 @@
import { Eltro as t, assert} from 'eltro'
import * as helpers from './helpers.mjs'
import Nconf from '../lib/nconf.mjs'
var globalConfig = helpers.fixture('hierarchy/global.json')
var userConfig = helpers.fixture('hierarchy/user.json')
var file3 = helpers.fixture('merge/file3.json')
t.test('configured with two file stores should work', function() {
let nconf = new Nconf()
nconf.file('user', {type: 'file', file: userConfig})
nconf.file('global', {type: 'file', file: globalConfig})
nconf.load()
assert.strictEqual(nconf.get('title'), 'My specific title')
assert.strictEqual(nconf.get('color'), 'green')
assert.strictEqual(nconf.get('movie'), 'Kill Bill')
})
t.test('configured with two file stores with just filenames should work', function() {
let nconf = new Nconf()
nconf.file('user', userConfig)
nconf.file('global', globalConfig)
nconf.load()
assert.strictEqual(nconf.get('title'), 'My specific title')
assert.strictEqual(nconf.get('color'), 'green')
assert.strictEqual(nconf.get('movie'), 'Kill Bill')
})
t.test('configured with .file(), .defaults() should deep merge objects correctly', async function() {
let nconf = new Nconf()
.file('localOverrides', file3)
.defaults({
"candy": {
"something": "a nice default",
"something1": true,
"something2": true,
"something5": {
"first": 1,
"second": 2
}
}
})
assert.deepStrictEqual(nconf.get('candy'), {
something: 'much better something for you',
something1: true,
something2: true,
something18: 'completely unique',
something5: {
first: 1,
second: 99
}
})
})

View File

@ -1,38 +0,0 @@
/*
* mock-store.js: Mock store for ensuring certain operations are actually called.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var util = require('util'),
events = require('events'),
nconf = require('../../lib/nconf');
var Mock = nconf.Mock = function () {
events.EventEmitter.call(this);
this.type = 'mock';
};
// Inherit from Memory store.
util.inherits(Mock, events.EventEmitter);
//
// ### function save (value, callback)
// #### @value {Object} _Ignored_ Left here for consistency
// #### @callback {function} Continuation to respond to when complete.
// Waits `1000ms` and then calls the callback and emits the `save` event.
//
Mock.prototype.save = function (value, callback) {
if (!callback && typeof value === 'function') {
callback = value;
value = null;
}
var self = this;
setTimeout(function () {
self.emit('save');
callback();
}, 1000);
};

View File

@ -1,132 +0,0 @@
/*
* file-store-test.js: Tests for the nconf File store.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
path = require('path'),
vows = require('vows'),
assert = require('assert'),
nconf = require('../lib/nconf'),
data = require('./fixtures/data').data;
vows.describe('nconf').addBatch({
"When using the nconf": {
"should have the correct methods set": function () {
assert.isFunction(nconf.key);
assert.isFunction(nconf.path);
assert.isFunction(nconf.use);
assert.isFunction(nconf.get);
assert.isFunction(nconf.set);
assert.isFunction(nconf.clear);
assert.isFunction(nconf.load);
assert.isFunction(nconf.save);
assert.isFunction(nconf.reset);
},
"the use() method": {
"should instaniate the correct store": function () {
nconf.use('memory');
nconf.load();
assert.instanceOf(nconf.stores['memory'], nconf.Memory);
}
},
"it should": {
topic: function () {
fs.readFile(path.join(__dirname, '..', 'package.json'), this.callback);
},
"have the correct version set": function (err, data) {
assert.isNull(err);
data = JSON.parse(data.toString());
assert.equal(nconf.version, data.version);
}
}
}
}).addBatch({
"When using the nconf": {
"with the memory store": {
"the set() method": {
"should respond with true": function () {
assert.isTrue(nconf.set('foo:bar:bazz', 'buzz'));
}
},
"the get() method": {
"without a callback": {
"should respond with the correct value": function () {
assert.equal(nconf.get('foo:bar:bazz'), 'buzz');
}
},
"with a callback": {
topic: function () {
nconf.get('foo:bar:bazz', this.callback);
},
"should respond with the correct value": function (err, value) {
assert.equal(value, 'buzz');
}
}
}
}
}
}).addBatch({
"When using the nconf": {
"with the memory store": {
"the get() method": {
"should respond allow access to the root": function () {
assert(nconf.get(null));
assert(nconf.get(undefined));
assert(nconf.get());
}
},
"the set() method": {
"should respond allow access to the root and complain about non-objects": function () {
assert(!nconf.set(null, null));
assert(!nconf.set(null, undefined));
assert(!nconf.set(null));
assert(!nconf.set(null, ''));
assert(!nconf.set(null, 1));
var original = nconf.get();
assert(nconf.set(null, nconf.get()));
assert.notEqual(nconf.get(), original);
assert.deepEqual(nconf.get(), original)
}
}
}
}
}).addBatch({
"When using nconf": {
"with the memory store": {
"the clear() method": {
"should respond with the true": function () {
assert.equal(nconf.get('foo:bar:bazz'), 'buzz');
assert.isTrue(nconf.clear('foo:bar:bazz'));
assert.isTrue(typeof nconf.get('foo:bar:bazz') === 'undefined');
}
},
"the load() method": {
"without a callback": {
"should respond with the merged store": function () {
assert.deepEqual(nconf.load(), {
title: 'My specific title',
color: 'green',
movie: 'Kill Bill'
});
}
},
"with a callback": {
topic: function () {
nconf.load(this.callback.bind(null, null));
},
"should respond with the merged store": function (ign, err, store) {
assert.isNull(err);
assert.deepEqual(store, {
title: 'My specific title',
color: 'green',
movie: 'Kill Bill'
});
}
}
}
}
}
}).export(module);

194
test/nconf.test.mjs Normal file
View File

@ -0,0 +1,194 @@
import fs from 'fs'
import path from 'path'
import { Eltro as t, assert} from 'eltro'
import Nconf from '../lib/nconf.mjs'
import * as helpers from './helpers.mjs'
t.describe('nconf', function() {
t.test('should have the correct methods set', function() {
let nconf = new Nconf()
assert.strictEqual(typeof(nconf.key), 'function')
assert.strictEqual(typeof(nconf.path), 'function')
assert.strictEqual(typeof(nconf.use), 'function')
assert.strictEqual(typeof(nconf.any), 'function')
assert.strictEqual(typeof(nconf.get), 'function')
assert.strictEqual(typeof(nconf.set), 'function')
assert.strictEqual(typeof(nconf.clear), 'function')
assert.strictEqual(typeof(nconf.load), 'function')
assert.strictEqual(typeof(nconf.save), 'function')
assert.strictEqual(typeof(nconf.reset), 'function')
assert.strictEqual(typeof(nconf.required), 'function')
})
t.test('memory() should instaniate the correct store', function() {
let nconf = new Nconf()
nconf.memory()
assert.ok(nconf.use('memory') instanceof Nconf.Memory)
})
t.test('should have the correct version set', function () {
let nconf = new Nconf()
let pckg = JSON.parse(fs.readFileSync(helpers.fixture('../../package.json')))
assert.ok(pckg.version)
assert.strictEqual(nconf.version, pckg.version)
})
})
t.describe('#required()', function() {
let nconf = new Nconf()
nconf.memory()
nconf.set('foo:bar:bazz', 'buzz')
t.test('should throw error with missing keys', function() {
assert.throws(function() {
nconf.required(['missingtest', 'foo:bar:bazz'])
}, /missingtest/)
})
t.test('should throw error with missing keys with non-array parameter', function() {
assert.throws(function() {
nconf.required(['missingtest', 'foo:bar:bazz'])
}, /missingtest/)
})
t.test('should return the provider if all keys exist', function() {
assert.strictEqual(nconf.required(['foo:bar:bazz']), nconf)
})
})
t.describe('#any()', function() {
let nconf = new Nconf()
nconf.memory()
nconf.set('foo:bar:bazz', 'buzz')
t.test('should return if found', function() {
assert.strictEqual(nconf.any(['missingtest', 'nope', 'foo:bar:bazz']), 'buzz')
})
t.test('should return first item found', function() {
assert.deepStrictEqual(nconf.any(['missingtest', 'foo', 'nope', 'foo:bar:bazz']), {
bar: {
bazz: 'buzz',
},
})
})
t.test('should return if found as paramaters', function() {
assert.strictEqual(nconf.any('missingtest', 'nope', 'foo:bar:bazz'), 'buzz')
})
t.test('should return undefined otherwise', function() {
assert.strictEqual(nconf.any(['missingtest', 'nope']), undefined)
})
})
t.describe('#set()', function() {
t.test('should respond with self if success', function() {
let nconf = new Nconf()
nconf.memory()
assert.strictEqual(nconf.set('foo:bar:bazz', 'buzz'), nconf)
})
t.test('should respond with false if not successful', function() {
let nconf = new Nconf()
nconf.memory({ readOnly: true })
assert.strictEqual(nconf.set('foo:bar:bazz', 'buzz'), false)
})
t.test('should always set the first writeable store', function() {
let nconf = new Nconf()
nconf.memory('first')
nconf.memory('second')
nconf.use('second').set('foo:bar:bazz', 'buzz')
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
nconf.set('foo:bar:bazz', 'overwritten')
assert.strictEqual(nconf.get('foo:bar:bazz'), 'overwritten')
assert.strictEqual(nconf.use('second').get('foo:bar:bazz'), 'buzz')
})
t.test('should respond allow access to the root and complain about non-objects', function() {
let nconf = new Nconf()
nconf.memory()
assert.notOk(nconf.set(null, null))
assert.notOk(nconf.set(null, undefined))
assert.notOk(nconf.set(null))
assert.notOk(nconf.set(null, ''))
assert.notOk(nconf.set(null, 1))
var original = nconf.get()
assert.ok(nconf.set(null, nconf.get()))
assert.notStrictEqual(nconf.get(), original)
assert.deepStrictEqual(nconf.get(), original)
})
})
t.describe('#get()', function() {
let nconf = new Nconf()
nconf.memory()
nconf.set('foo:bar:bazz', 'buzz')
t.test('should respond with the correct value', function() {
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
})
t.test('unknown keys should return undefined', function() {
assert.strictEqual(nconf.get('foo:bar:bazz:toString'), undefined)
})
t.test('should not step inside strings', function() {
assert.strictEqual(nconf.get('foo:bar:bazz:0'), undefined)
})
t.test('should respond allow access to the root', function() {
assert.ok(nconf.get(null))
assert.ok(nconf.get(undefined))
assert.ok(nconf.get())
assert.deepStrictEqual(nconf.get(), { foo: { bar: { bazz: 'buzz' } } })
})
t.test('should merge stores correctly', function() {
let testMerge = new Nconf()
testMerge.memory('higherpriority')
testMerge.set('foo:bar', {
bazz: 'overwritten',
test: 1
})
testMerge.memory('lowerdefaults')
testMerge.use('lowerdefaults').set('foo:bar:bazz', 'buzz')
testMerge.use('lowerdefaults').set('foo:bar:buzz', 'buzz')
assert.strictEqual(testMerge.get('foo:bar:bazz'), 'overwritten')
assert.strictEqual(testMerge.get('foo:bar:buzz'), 'buzz')
assert.deepStrictEqual(testMerge.get('foo:bar'), {
bazz: 'overwritten',
buzz: 'buzz',
test: 1,
})
})
})
t.describe('#clear()', function() {
t.test('should respond with self if success', function() {
let nconf = new Nconf()
nconf.memory().set('foo:bar:bazz', 'buzz')
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
assert.strictEqual(nconf.clear('foo:bar:bazz'), nconf)
assert.strictEqual(nconf.get('foo:bar:bazz'), undefined)
})
t.test('should respond with self if success even with readOnly store', function() {
let nconf = new Nconf()
nconf
.literal({ testetytest: 'buzz' })
.memory()
.set('foo:bar:bazz', 'buzz')
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
assert.strictEqual(nconf.get('testetytest'), 'buzz')
assert.strictEqual(nconf.clear('foo:bar:bazz'), nconf)
assert.strictEqual(nconf.get('foo:bar:bazz'), undefined)
assert.strictEqual(nconf.use('literal').get('foo:bar:bazz'), undefined)
})
t.test('should respond with false if clearing readonly value', function() {
let nconf = new Nconf()
nconf.literal({ testetytest: 'buzz' })
assert.strictEqual(nconf.get('testetytest'), 'buzz')
assert.notOk(nconf.clear('testetytest'))
assert.strictEqual(nconf.get('testetytest'), 'buzz')
})
})

View File

@ -1,39 +0,0 @@
/*
* provider-save-test.js: Ensures consistency for Provider `save` operations.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var assert = require('assert'),
vows = require('vows'),
nconf = require('../lib/nconf');
//
// Expose `nconf.Mock`
//
require('./mocks/mock-store');
vows.describe('nconf/provider/save').addBatch({
"When using nconf": {
"an instance of 'nconf.Provider'": {
"with a Mock store": {
topic: function () {
return nconf.use('mock');
},
"the save() method": {
topic: function () {
var mock = nconf.stores.mock,
that = this;
mock.on('save', function () { that.saved = true });
nconf.save(this.callback);
},
"should actually save before responding": function () {
assert.isTrue(this.saved);
}
}
}
}
}
}).export(module);

View File

@ -1,170 +0,0 @@
/*
* provider-test.js: Tests for the nconf Provider object.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
spawn = require('child_process').spawn,
vows = require('vows'),
helpers = require('./helpers'),
nconf = require('../lib/nconf');
var fixturesDir = path.join(__dirname, 'fixtures'),
mergeFixtures = path.join(fixturesDir, 'merge'),
files = [path.join(mergeFixtures, 'file1.json'), path.join(mergeFixtures, 'file2.json')],
override = JSON.parse(fs.readFileSync(files[0]), 'utf8');
function assertProvider(test) {
return {
topic: new nconf.Provider(),
"should use the correct File store": test
};
}
vows.describe('nconf/provider').addBatch({
"When using nconf": {
"an instance of 'nconf.Provider'": {
"calling the use() method with the same store type and different options": {
topic: new nconf.Provider().use('file', { file: files[0] }),
"should use a new instance of the store type": function (provider) {
var old = provider.stores['file'];
assert.equal(provider.stores.file.file, files[0]);
provider.use('file', { file: files[1] });
assert.notStrictEqual(old, provider.stores.file);
assert.equal(provider.stores.file.file, files[1]);
}
},
"when 'argv' is true": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'provider-argv.js'),
argv: ['--something', 'foobar']
}),
"when 'env' is true": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'provider-env.js'),
env: { SOMETHING: 'foobar' }
})
},
"the default nconf provider": {
"when 'argv' is set to true": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'nconf-argv.js'),
argv: ['--something', 'foobar'],
env: { SOMETHING: true }
}),
"when 'env' is set to true": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'nconf-env.js'),
env: { SOMETHING: 'foobar' }
}),
"when 'argv' is set to true and process.argv is modified": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'nconf-change-argv.js'),
argv: ['--something', 'badValue', 'evenWorse', 'OHNOEZ', 'foobar']
}),
"when hierarchical 'argv' get": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'nconf-hierarchical-file-argv.js'),
argv: ['--something', 'foobar'],
env: { SOMETHING: true }
}),
"when 'env' is set to true with a nested separator": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'nconf-nested-env.js'),
env: { SOME_THING: 'foobar' }
})
}
}
}).addBatch({
"When using nconf": {
"an instance of 'nconf.Provider'": {
"the merge() method": {
topic: new nconf.Provider().use('file', { file: files[1] }),
"should have the result merged in": function (provider) {
provider.load();
provider.merge(override);
helpers.assertMerged(null, provider.stores.file.store);
assert.equal(provider.stores.file.store.candy.something, 'file1');
},
"should merge Objects over null": function (provider) {
provider.load();
provider.merge(override);
assert.equal(provider.stores.file.store.unicorn.exists, true);
}
}
}
}
}).addBatch({
"When using nconf": {
"an instance of 'nconf.Provider'": {
"the load() method": {
"when sources are passed in": {
topic: new nconf.Provider({
sources: {
user: {
type: 'file',
file: files[0]
},
global: {
type: 'file',
file: files[1]
}
}
}),
"should respect the hierarchy ": function (provider) {
var merged = provider.load();
helpers.assertMerged(null, merged);
assert.equal(merged.candy.something, 'file1');
}
},
"when multiple stores are used": {
topic: new nconf.Provider().overrides({foo: {bar: 'baz'}})
.add('file1', {type: 'file', file: files[0]})
.add('file2', {type: 'file', file: files[1]}),
"should respect the hierarchy": function(provider) {
var merged = provider.load();
helpers.assertMerged(null, merged);
assert.equal(merged.foo.bar, 'baz');
assert.equal(merged.candy.something, 'file1');
}
}
}
}
}
}).addBatch({
"When using nconf": {
"an instance of 'nconf.Provider'": {
"the .file() method": {
"with a single filepath": assertProvider(function (provider) {
provider.file(helpers.fixture('store.json'));
assert.isObject(provider.stores.file);
}),
"with a name and a filepath": assertProvider(function (provider) {
provider.file('custom', helpers.fixture('store.json'));
assert.isObject(provider.stores.custom);
}),
"with a single object": assertProvider(function (provider) {
provider.file({
dir: helpers.fixture(''),
file: 'store.json',
search: true
});
assert.isObject(provider.stores.file);
assert.equal(provider.stores.file.file, helpers.fixture('store.json'));
}),
"with a name and an object": assertProvider(function (provider) {
provider.file('custom', {
dir: helpers.fixture(''),
file: 'store.json',
search: true
});
assert.isObject(provider.stores.custom);
assert.equal(provider.stores.custom.file, helpers.fixture('store.json'));
})
}
}
}
}).export(module);

225
test/provider.test.mjs Normal file
View File

@ -0,0 +1,225 @@
import fs from 'fs'
import path from 'path'
import { spawn } from 'child_process'
import { Eltro as t, assert} from 'eltro'
import * as helpers from './helpers.mjs'
// import nconf from '../lib/nconf-o.mjs'
import Nconf from '../lib/nconf.mjs'
let files = [
helpers.fixture('merge/file1.json'),
helpers.fixture('merge/file2.json'),
];
let override = JSON.parse(fs.readFileSync(files[0]), 'utf8');
function assertSystemConf(options) {
return new Promise(function(res, rej) {
let env = null;
if (options.env) {
env = {}
Object.keys(process.env).forEach(function (key) {
env[key] = process.env[key];
});
Object.keys(options.env).forEach(function (key) {
env[key] = options.env[key];
});
}
let child = spawn('node', [options.script].concat(options.argv), {env: env});
child.stdout.once('data', data => {
res(data.toString())
});
})
}
t.describe('When using nconf', function() {
t.test("calling add with the same store type and different options should use the new instance", function() {
let nconf = new Nconf()
nconf.file({ file: files[0] })
let old = nconf.using.get('file');
assert.ok(old)
assert.strictEqual(old.file, files[0]);
nconf.file({ file: files[1] });
let newOne = nconf.using.get('file');
assert.notStrictEqual(old, newOne);
assert.strictEqual(newOne.file, files[1]);
})
/*
t.test("respond with correct arg when 'env' is true", async function() {
let result = await assertSystemConf({
script: helpers.fixture('scripts/provider-env.mjs'),
env: {SOMETHING: 'foobar'}
})
assert.strictEqual(result.toString(), 'foobar')
});
t.test("respond with correct arg when 'env' is true and 'parseValues' option is true", function() {
let env = {
SOMETHING: 'foobar',
SOMEBOOL: 'true',
SOMENULL: 'null',
SOMEUNDEF: 'undefined',
SOMEINT: '3600',
SOMEFLOAT: '0.5',
SOMEBAD: '5.1a'
};
let oenv = {};
Object.keys(env).forEach(function (key) {
if (process.env[key]) oenv[key] = process.env[key];
process.env[key] = env[key];
});
let provider = new nconf.Provider().use('env', {parseValues: true});
Object.keys(env).forEach(function (key) {
delete process.env[key];
if (oenv[key]) process.env[key] = oenv[key];
});
assert.strictEqual(provider.get('SOMETHING'), 'foobar');
assert.strictEqual(provider.get('SOMEBOOL'), true);
assert.notStrictEqual(provider.get('SOMEBOOL'), 'true');
assert.strictEqual(provider.get('SOMENULL'), null);
assert.strictEqual(provider.get('SOMEUNDEF'), undefined);
assert.strictEqual(provider.get('SOMEINT'), 3600);
assert.strictEqual(provider.get('SOMEFLOAT'), .5);
assert.strictEqual(provider.get('SOMEBAD'), '5.1a');
});
t.describe("an instance of 'nconf.Provider'", function() {
t.describe("the merge() method", function() {
t.test("should have the result merged in", function() {
let provider = new nconf.Provider().use('file', {file: files[1]});
provider.load();
provider.merge(override);
helpers.assertMerged(null, provider.stores.file.store);
assert.strictEqual(provider.stores.file.store.candy.something, 'file1');
});
t.test("should merge Objects over null", function() {
let provider = new nconf.Provider().use('file', {file: files[1]});
provider.load();
provider.merge(override);
assert.strictEqual(provider.stores.file.store.unicorn.exists, true);
});
})
t.describe("the load() method", function() {
t.test("should respect the hierarchy when sources are passed in", function() {
let provider = new nconf.Provider({
sources: {
user: {
type: 'file',
file: files[0]
},
global: {
type: 'file',
file: files[1]
}
}
});
let merged = provider.load();
helpers.assertMerged(null, merged);
assert.strictEqual(merged.candy.something, 'file1');
})
t.test("should respect the hierarchy when multiple stores are used", function() {
let provider = new nconf.Provider().overrides({foo: {bar: 'baz'}})
.add('file1', {type: 'file', file: files[0]})
.add('file2', {type: 'file', file: files[1]});
let merged = provider.load();
helpers.assertMerged(null, merged);
assert.strictEqual(merged.foo.bar, 'baz');
assert.strictEqual(merged.candy.something, 'file1');
})
})
})
t.describe("the .file() method", function() {
t.test("should use the correct File store with a single filepath", function() {
let provider = new nconf.Provider();
provider.file(helpers.fixture('store.json'));
assert.strictEqual(typeof(provider.stores.file), 'object');
});
t.test("should use the correct File store with a name and a filepath", function() {
let provider = new nconf.Provider();
provider.file('custom', helpers.fixture('store.json'));
assert.strictEqual(typeof(provider.stores.custom), 'object');
});
t.test("should use the correct File store with a single object", function() {
let provider = new nconf.Provider();
provider.file({
dir: helpers.fixture(''),
file: 'store.json',
search: true
});
assert.strictEqual(typeof(provider.stores.file), 'object');
assert.strictEqual(provider.stores.file.file, helpers.fixture('store.json'));
});
t.test("should use the correct File store with a name and an object", function() {
let provider = new nconf.Provider();
provider.file('custom', {
dir: helpers.fixture(''),
file: 'store.json',
search: true
});
assert.strictEqual(typeof(provider.stores.custom), 'object');
assert.strictEqual(provider.stores.custom.file, helpers.fixture('store.json'));
})*/
/*t.describe("the any() method", function() {
let provider = new nconf.Provider({
type: 'literal',
store: {
key: "getThisValue"
}
})
t.describe("without a callback", function() {
t.test("should respond with the correct value given an array of keys with one matching", function() {
assert.strictEqual(provider.any(["notthis", "orthis", "key"]), 'getThisValue');
})
t.test("should respond with null given an array of keys with no match", function() {
assert.strictEqual(provider.any(["notthis", "orthis"]), null);
});
t.test("should respond with the correct value given a variable argument list of keys with one matching", function() {
assert.strictEqual(provider.any("notthis", "orthis", "key"), 'getThisValue');
});
t.test("should respond with null given no arguments", function() {
assert.strictEqual(provider.any(), null);
});
})
t.describe("with a callback", function() {
t.test("should respond with the correct value given an array of keys with one matching", function(done) {
provider.any(["notthis", "orthis", "key"], (err, value) => {
assert.strictEqual(value, 'getThisValue');
done();
});
});
t.test("should respond with an undefined value given an array of keys with no match", function(done) {
provider.any(["notthis", "orthis"], (err, value) => {
assert.strictEqual(value, undefined)
done();
});
});
t.test("should respond with the correct value given a variable argument list of keys with one matching", function(done) {
provider.any("notthis", "orthis", "key", (err, value) => {
assert.strictEqual(value, 'getThisValue');
done();
});
});
t.test("should respond with an undefined value given no keys", function(done) {
provider.any((err, value) => {
assert.strictEqual(value, undefined)
done();
});
});
})
})
})*/
});

View File

@ -1,22 +0,0 @@
/*
* argv-test.js: Tests for the nconf argv store.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/argv').addBatch({
"An instance of nconf.Argv": {
topic: new nconf.Argv(),
"should have the correct methods defined": function (argv) {
assert.isFunction(argv.loadSync);
assert.isFunction(argv.loadArgv);
assert.isFalse(argv.options);
}
}
}).export(module);

171
test/stores/argv.test.mjs Normal file
View File

@ -0,0 +1,171 @@
import { Eltro as t, assert} from 'eltro'
import Nconf from '../../lib/nconf.mjs'
t.describe('#load()', () => {
t.test('should support pairs of arguments together as strings', function() {
process.argv = ['bla', 'bla',
'--foobar', '123',
'--testety', 'hello',
]
let store = new Nconf.Argv()
store.load()
assert.strictEqual(store.get('foobar'), '123')
assert.strictEqual(store.get('testety'), 'hello')
assert.deepStrictEqual(store.get(), {
foobar: '123',
testety: 'hello',
})
})
t.test('should support custom prefix argument', function() {
process.argv = ['bla', 'bla',
'-foobar', '123',
'-testety', 'hello',
]
let store = new Nconf.Argv({ prefix: '-' })
store.load()
assert.strictEqual(store.get('foobar'), '123')
assert.strictEqual(store.get('testety'), 'hello')
assert.deepStrictEqual(store.get(), {
foobar: '123',
testety: 'hello',
})
})
t.test('should support individual options as boolean', function() {
process.argv = ['bla', 'bla',
'--booleanone',
'--foobar', '123',
'--testety', 'hello',
'--booleantwo',
]
let store = new Nconf.Argv()
store.load()
assert.strictEqual(store.get('foobar'), '123')
assert.strictEqual(store.get('testety'), 'hello')
assert.strictEqual(store.get('booleanone'), true)
assert.strictEqual(store.get('booleantwo'), true)
assert.deepStrictEqual(store.get(), {
foobar: '123',
testety: 'hello',
booleanone: true,
booleantwo: true,
})
})
t.test('should support forcing everything as lowercase', function() {
process.argv = ['bla', 'bla',
'--FOOBAR', '123',
'--TESTETY', 'hello',
]
let store = new Nconf.Argv({ lowerCase: true })
store.load()
assert.strictEqual(store.get('foobar'), '123')
assert.strictEqual(store.get('testety'), 'hello')
assert.deepStrictEqual(store.get(), {
foobar: '123',
testety: 'hello',
})
})
t.test('should support making objects', function() {
process.argv = ['bla', 'bla',
'--foo:bar:baz', '123',
'--foo:bar:testety', 'hello',
]
let store = new Nconf.Argv()
store.load()
assert.strictEqual(store.get('foo:bar:baz'), '123')
assert.strictEqual(store.get('foo:bar:testety'), 'hello')
assert.deepStrictEqual(store.get(), {
foo: {
bar: {
baz: '123',
testety: 'hello',
}
}
})
})
t.test('should support custom seperator', function() {
process.argv = ['bla', 'bla',
'--foo__bar__baz', '123',
'--foo__bar__testety', 'hello',
]
let store = new Nconf.Argv({ separator: '__' })
store.load()
assert.strictEqual(store.get('foo:bar:baz'), '123')
assert.strictEqual(store.get('foo:bar:testety'), 'hello')
assert.deepStrictEqual(store.get(), {
foo: {
bar: {
baz: '123',
testety: 'hello',
}
}
})
})
t.test('should support parse values', function() {
process.argv = ['bla', 'bla',
'--foo', '123',
'--bar', '0.123',
'--asdf', '{"hello":"world"}',
'--testety', 'hello',
]
let store = new Nconf.Argv({ parseValues: true })
store.load()
assert.deepStrictEqual(store.get(), {
foo: 123,
bar: 0.123,
asdf: {
hello: 'world',
},
testety: 'hello',
})
})
t.test('should support usage of equal sign instead', function() {
process.argv = ['bla', 'bla',
'--foo=123',
'--testety=hello',
]
let store = new Nconf.Argv({ useEqualsign: true })
store.load()
assert.deepStrictEqual(store.get(), {
foo: '123',
testety: 'hello',
})
})
t.test('equal sign should support boolean values', function() {
process.argv = ['bla', 'bla',
'--booleanone',
'--foobar=123',
'--testety=hello',
'--booleantwo',
]
let store = new Nconf.Argv({ useEqualsign: true })
store.load()
assert.strictEqual(store.get('booleanone'), true)
assert.strictEqual(store.get('booleantwo'), true)
assert.deepStrictEqual(store.get(), {
foobar: '123',
testety: 'hello',
booleanone: true,
booleantwo: true,
})
})
t.test('should be smart with the usage of equal sign', function() {
process.argv = ['bla', 'bla',
'--foo=hello=world',
]
let store = new Nconf.Argv({ useEqualsign: true })
store.load()
assert.strictEqual(store.get('foo'), 'hello=world')
})
})

View File

@ -1,24 +0,0 @@
/*
* env-test.js: Tests for the nconf env store.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/env').addBatch({
"An instance of nconf.Env": {
topic: new nconf.Env(),
"should have the correct methods defined": function (env) {
assert.isFunction(env.loadSync);
assert.isFunction(env.loadEnv);
assert.isArray(env.whitelist);
assert.lengthOf(env.whitelist, 0);
assert.equal(env.separator, '');
}
}
}).export(module);

245
test/stores/env.test.mjs Normal file
View File

@ -0,0 +1,245 @@
import { Eltro as t, assert} from 'eltro'
import Nconf from '../../lib/nconf.mjs'
t.describe('#load()', () => {
let backupEnv = {}
let testEnv = {
SOMETHING: 'foobar',
SOMEBOOL: 'true',
SOMENULL: 'null',
SOMEUNDEF: 'undefined',
SOMEINT: '3600',
SOMEFLOAT: '0.5',
SOMEBAD: '5.1a',
ANOTHER__TEST__THIS: 'foobar',
}
t.before(function() {
Object.keys(testEnv).forEach(function (key) {
if (process.env[key]) backupEnv[key] = process.env[key]
process.env[key] = testEnv[key]
})
})
t.test("should default read everything as string", function() {
let store = new Nconf.Env()
store.load()
assert.strictEqual(store.get('SOMETHING'), 'foobar')
assert.strictEqual(store.get('SOMEBOOL'), 'true')
assert.strictEqual(store.get('SOMENULL'), 'null')
assert.strictEqual(store.get('SOMEUNDEF'), 'undefined')
assert.strictEqual(store.get('SOMEINT'), '3600')
assert.strictEqual(store.get('SOMEFLOAT'), '0.5')
assert.strictEqual(store.get('SOMEBAD'), '5.1a')
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
})
t.test("should support parseValues correctly", function() {
let store = new Nconf.Env({parseValues: true})
store.load()
assert.strictEqual(store.get('SOMETHING'), 'foobar')
assert.strictEqual(store.get('SOMEBOOL'), true)
assert.notStrictEqual(store.get('SOMEBOOL'), 'true')
assert.strictEqual(store.get('SOMENULL'), null)
assert.strictEqual(store.get('SOMEUNDEF'), undefined)
assert.strictEqual(store.get('SOMEINT'), 3600)
assert.strictEqual(store.get('SOMEFLOAT'), .5)
assert.strictEqual(store.get('SOMEBAD'), '5.1a')
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
})
t.test("should support lowercase", function() {
let store = new Nconf.Env({lowerCase: true})
store.load()
assert.notOk(store.get('SOMETHING'))
assert.notOk(store.get('SOMEBOOL'))
assert.notOk(store.get('SOMENULL'))
assert.notOk(store.get('SOMEUNDEF'))
assert.notOk(store.get('SOMEINT'))
assert.notOk(store.get('SOMEFLOAT'))
assert.notOk(store.get('SOMEBAD'))
assert.notOk(store.get('ANOTHER__TEST__THIS'))
assert.strictEqual(store.get('something'), 'foobar')
assert.strictEqual(store.get('somebool'), 'true')
assert.strictEqual(store.get('somenull'), 'null')
assert.strictEqual(store.get('someundef'), 'undefined')
assert.strictEqual(store.get('someint'), '3600')
assert.strictEqual(store.get('somefloat'), '0.5')
assert.strictEqual(store.get('somebad'), '5.1a')
assert.strictEqual(store.get('another__test__this'), 'foobar')
})
t.test("should support transform", function() {
let store = new Nconf.Env({transform: function(key, value) {
if (!testEnv[key]) return null
return {
key: key[0].toUpperCase() + key.slice(1).toLowerCase(),
value: 1,
}
}})
store.load()
assert.strictEqual(store.get('Something'), 1)
assert.strictEqual(store.get('Somebool'), 1)
assert.strictEqual(store.get('Somenull'), 1)
assert.strictEqual(store.get('Someundef'), 1)
assert.strictEqual(store.get('Someint'), 1)
assert.strictEqual(store.get('Somefloat'), 1)
assert.strictEqual(store.get('Somebad'), 1)
assert.strictEqual(store.get('Another__test__this'), 1)
assert.deepStrictEqual(store.get(), {
Something: 1,
Somebool: 1,
Somenull: 1,
Someundef: 1,
Someint: 1,
Somefloat: 1,
Somebad: 1,
Another__test__this: 1,
})
})
t.test("should support matches", function() {
let store = new Nconf.Env({match: /^SOME/})
store.load()
assert.strictEqual(store.get('SOMETHING'), 'foobar')
assert.strictEqual(store.get('SOMEBOOL'), 'true')
assert.strictEqual(store.get('SOMENULL'), 'null')
assert.strictEqual(store.get('SOMEUNDEF'), 'undefined')
assert.strictEqual(store.get('SOMEINT'), '3600')
assert.strictEqual(store.get('SOMEFLOAT'), '0.5')
assert.strictEqual(store.get('SOMEBAD'), '5.1a')
assert.notOk(store.get('Another__test__this'))
assert.deepStrictEqual(store.get(), {
SOMETHING: 'foobar',
SOMEBOOL: 'true',
SOMENULL: 'null',
SOMEUNDEF: 'undefined',
SOMEINT: '3600',
SOMEFLOAT: '0.5',
SOMEBAD: '5.1a',
})
})
t.test("should support whitelist", function() {
let store = new Nconf.Env({whitelist: ['ANOTHER__TEST__THIS']})
store.load()
assert.notOk(store.get('SOMETHING'), 'foobar')
assert.notOk(store.get('SOMEBOOL'), 'true')
assert.notOk(store.get('SOMENULL'), 'null')
assert.notOk(store.get('SOMEUNDEF'), 'undefined')
assert.notOk(store.get('SOMEINT'), '3600')
assert.notOk(store.get('SOMEFLOAT'), '0.5')
assert.notOk(store.get('SOMEBAD'), '5.1a')
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
assert.deepStrictEqual(store.get(), {
ANOTHER__TEST__THIS: 'foobar',
})
})
t.test("whitelist should be case insensitive", function() {
let store = new Nconf.Env({whitelist: ['another__test__this']})
store.load()
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
assert.deepStrictEqual(store.get(), {
ANOTHER__TEST__THIS: 'foobar',
})
})
t.test("should support whitelist with match", function() {
let store = new Nconf.Env({
whitelist: ['another__test__this'],
match: /^SOMEBOOL/,
})
store.load()
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
assert.strictEqual(store.get('SOMEBOOL'), 'true')
assert.deepStrictEqual(store.get(), {
ANOTHER__TEST__THIS: 'foobar',
SOMEBOOL: 'true',
})
})
t.test("should support custom seperator", function() {
let store = new Nconf.Env({
whitelist: ['another__test__this', 'somebool'],
separator: '__',
})
store.load()
assert.strictEqual(store.get('ANOTHER:TEST:THIS'), 'foobar')
assert.strictEqual(store.get('SOMEBOOL'), 'true')
assert.deepStrictEqual(store.get(), {
ANOTHER: {
TEST: {
THIS: 'foobar',
},
},
SOMEBOOL: 'true',
})
})
t.test("should stay readOnly always", function() {
let store = new Nconf.Env({whitelist: ['another__test__this']})
assert.strictEqual(store.readOnly, true)
store.load()
assert.strictEqual(store.readOnly, true)
})
t.test("should throw if whitelist is invalid", function() {
assert.throws(function() {
new Nconf.Env({whitelist: 'another__test__this'})
}, /[Ww]hitelist.+[Aa]rray/)
assert.throws(function() {
new Nconf.Env({whitelist: ['another__test__this', 123]})
}, /[Ww]hitelist.+[Aa]rray/)
})
t.test("should throw if match is invalid", function() {
assert.throws(function() {
new Nconf.Env({match: 1234})
}, /[Mm]atch.+[Rr]eg[Ee]xp/)
assert.throws(function() {
new Nconf.Env({match: {}})
}, /[Mm]atch.+[Rr]eg[Ee]xp/)
})
t.test("should automatically convert string match to RegExp", function() {
let store = new Nconf.Env({match: 'asdf'})
assert.ok(store.match)
assert.ok(store.match.test('asdf'))
assert.notOk(store.match.test('test'))
})
t.test("should support whitelist directly in parameter", function() {
let store = new Nconf.Env(['another__test__this'])
store.load()
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
assert.deepStrictEqual(store.get(), {
ANOTHER__TEST__THIS: 'foobar',
})
})
t.after(function() {
Object.keys(backupEnv).forEach(function (key) {
process.env[key] = backupEnv[key]
})
})
})

View File

@ -1,261 +0,0 @@
/*
* file-store-test.js: Tests for the nconf File store.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var fs = require('fs'),
path = require('path'),
vows = require('vows'),
assert = require('assert'),
nconf = require('../../lib/nconf'),
data = require('../fixtures/data').data,
store;
vows.describe('nconf/stores/file').addBatch({
"When using the nconf file store": {
"with a valid JSON file": {
topic: function () {
var filePath = path.join(__dirname, '..', 'fixtures', 'store.json');
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
this.store = store = new nconf.File({ file: filePath });
return null;
},
"the load() method": {
topic: function () {
this.store.load(this.callback);
},
"should load the data correctly": function (err, data) {
assert.isNull(err);
assert.deepEqual(data, this.store.store);
}
}
},
"with a malformed JSON file": {
topic: function () {
var filePath = path.join(__dirname, '..', 'fixtures', 'malformed.json');
this.store = new nconf.File({ file: filePath });
return null;
},
"the load() method with a malformed JSON config file": {
topic: function () {
this.store.load(this.callback.bind(null, null));
},
"should respond with an error and indicate file name": function (_, err) {
assert.isTrue(!!err);
assert.match(err, /malformed\.json/);
}
}
},
"with a valid UTF8 JSON file that contains a BOM": {
topic: function () {
var filePath = path.join(__dirname, '..', 'fixtures', 'bom.json');
this.store = store = new nconf.File({ file: filePath });
return null;
},
"the load() method": {
topic: function () {
this.store.load(this.callback);
},
"should load the data correctly": function (err, data) {
assert.isNull(err);
assert.deepEqual(data, this.store.store);
}
},
"the loadSync() method": {
topic: function () {
var data = this.store.loadSync();
return data;
},
"should load the data correctly": function (result) {
assert.deepEqual(result, this.store.store);
}
}
},
"with a valid UTF8 JSON file that contains no BOM": {
topic: function () {
var filePath = path.join(__dirname, '..', 'fixtures', 'no-bom.json');
this.store = store = new nconf.File({ file: filePath });
return null;
},
"the load() method": {
topic: function () {
this.store.load(this.callback);
},
"should load the data correctly": function (err, data) {
assert.isNull(err);
assert.deepEqual(data, this.store.store);
}
},
"the loadSync() method": {
topic: function () {
var data = this.store.loadSync();
return data;
},
"should load the data correctly": function (result) {
assert.deepEqual(result, this.store.store);
}
}
}
}
}).addBatch({
"When using the nconf file store": {
topic: function () {
var tmpPath = path.join(__dirname, '..', 'fixtures', 'tmp.json'),
tmpStore = new nconf.File({ file: tmpPath });
return tmpStore;
},
"the save() method": {
topic: function (tmpStore) {
var that = this;
Object.keys(data).forEach(function (key) {
tmpStore.set(key, data[key]);
});
tmpStore.save(function () {
fs.readFile(tmpStore.file, function (err, d) {
fs.unlinkSync(tmpStore.file);
return err
? that.callback(err)
: that.callback(err, JSON.parse(d.toString()));
});
});
},
"should save the data correctly": function (err, read) {
assert.isNull(err);
assert.deepEqual(read, data);
}
}
}
}).addBatch({
"When using the nconf file store": {
topic: function () {
var tmpPath = path.join(__dirname, '..', 'fixtures', 'tmp.json'),
tmpStore = new nconf.File({ file: tmpPath });
return tmpStore;
},
"the saveSync() method": {
topic: function (tmpStore) {
var that = this;
Object.keys(data).forEach(function (key) {
tmpStore.set(key, data[key]);
});
var saved = tmpStore.saveSync();
fs.readFile(tmpStore.file, function (err, d) {
fs.unlinkSync(tmpStore.file);
return err
? that.callback(err)
: that.callback(err, JSON.parse(d.toString()), saved);
});
},
"should save the data correctly": function (err, read, saved) {
assert.isNull(err);
assert.deepEqual(read, data);
assert.deepEqual(read, saved);
}
}
}
}).addBatch({
"When using the nconf file store": {
"the set() method": {
"should respond with true": function () {
assert.isTrue(store.set('foo:bar:bazz', 'buzz'));
assert.isTrue(store.set('falsy:number', 0));
assert.isTrue(store.set('falsy:string', ''));
assert.isTrue(store.set('falsy:boolean', false));
assert.isTrue(store.set('falsy:object', null));
}
},
"the get() method": {
"should respond with the correct value": function () {
assert.equal(store.get('foo:bar:bazz'), 'buzz');
assert.equal(store.get('falsy:number'), 0);
assert.equal(store.get('falsy:string'), '');
assert.equal(store.get('falsy:boolean'), false);
assert.equal(store.get('falsy:object'), null);
}
},
"the clear() method": {
"should respond with the true": function () {
assert.equal(store.get('foo:bar:bazz'), 'buzz');
assert.isTrue(store.clear('foo:bar:bazz'));
assert.isTrue(typeof store.get('foo:bar:bazz') === 'undefined');
}
}
}
}).addBatch({
"When using the nconf file store": {
"the search() method": {
"when the target file exists higher in the directory tree": {
topic: function () {
var filePath = this.filePath = path.join(process.env.HOME, '.nconf');
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
return new (nconf.File)({
file: '.nconf'
})
},
"should update the file appropriately": function (store) {
store.search();
assert.equal(store.file, this.filePath);
fs.unlinkSync(this.filePath);
}
},
"when the target file doesn't exist higher in the directory tree": {
topic: function () {
var filePath = this.filePath = path.join(__dirname, '..', 'fixtures', 'search-store.json');
return new (nconf.File)({
dir: path.dirname(filePath),
file: 'search-store.json'
})
},
"should update the file appropriately": function (store) {
store.search();
assert.equal(store.file, this.filePath);
}
}
}
}
}).addBatch({
"When using the nconf file store": {
topic: function () {
var secureStore = new nconf.File({
file: path.join(__dirname, '..', 'fixtures', 'secure.json'),
secure: 'super-secretzzz'
});
secureStore.store = data;
return secureStore;
},
"the stringify() method should encrypt properly": function (store) {
var contents = JSON.parse(store.stringify());
Object.keys(data).forEach(function (key) {
assert.isObject(contents[key]);
assert.isString(contents[key].value);
assert.equal(contents[key].alg, 'aes-256-ctr');
});
},
"the parse() method should decrypt properly": function (store) {
var contents = store.stringify();
var parsed = store.parse(contents);
assert.deepEqual(parsed, data);
},
"the load() method should decrypt properly": function (store) {
store.load(function (err, loaded) {
assert.isNull(err);
assert.deepEqual(loaded, data);
});
},
"the loadSync() method should decrypt properly": function (store) {
var loaded = store.loadSync()
assert.deepEqual(loaded, data);
}
}
}).export(module);

250
test/stores/file.test.mjs Normal file
View File

@ -0,0 +1,250 @@
import fs from 'fs'
import { Eltro as t, assert} from 'eltro'
import * as helpers from '../helpers.mjs'
import { data } from '../fixtures/data.mjs'
import Nconf from '../../lib/nconf.mjs'
const fsPromise = fs.promises
// let yamlFormat = require('nconf-yaml')
t.describe('#load()', function() {
let validFile = helpers.fixture('store.json')
fs.writeFileSync(validFile, JSON.stringify(data, null, 2))
let malformedFile = helpers.fixture('malformed.json')
let bomFile = helpers.fixture('bom.json')
let noBomPath = helpers.fixture('no-bom.json')
t.test('with valid json should load correctly', function() {
let store = new Nconf.File({file: validFile})
store.load()
assert.deepStrictEqual(store.store, data)
})
t.test('with only file string as option', function() {
let store = new Nconf.File(validFile)
store.load()
assert.deepStrictEqual(store.store, data)
})
t.test('malformed JSON should respond with an error and indicate file name', function() {
let store = new Nconf.File({file: malformedFile})
assert.throws(() => {
store.load()
}, /malformed\.json/)
})
t.test('with a valid UTF8 JSON file that contains a BOM', function() {
let store = new Nconf.File(bomFile)
store.load()
assert.strictEqual(store.get('port'), 78304)
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
})
t.test('with a valid UTF8 JSON file that contains no BOM', function() {
let store = new Nconf.File(noBomPath)
store.load()
assert.strictEqual(store.get('port'), 78304)
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
})
})
t.describe('#loadAsync()', function() {
let validFile = helpers.fixture('store.json')
fs.writeFileSync(validFile, JSON.stringify(data, null, 2))
let malformedFile = helpers.fixture('malformed.json')
let bomFile = helpers.fixture('bom.json')
let noBomPath = helpers.fixture('no-bom.json')
t.test('with valid json should load correctly', function() {
let store = new Nconf.File({file: validFile})
return store.loadAsync().then(function(newData) {
assert.strictEqual(newData, store.store)
assert.deepStrictEqual(store.store, data)
})
})
t.test('with only file string as option', function() {
let store = new Nconf.File(validFile)
return store.loadAsync().then(function() {
assert.deepStrictEqual(store.store, data)
})
})
t.test('malformed JSON should respond with an error and indicate file name', function() {
let store = new Nconf.File({file: malformedFile})
assert.isRejected(store.loadAsync()).then(function(err) {
assert.match(err.message, /malformed\.json/)
})
})
t.test('with a valid UTF8 JSON file that contains a BOM', function() {
let store = new Nconf.File(bomFile)
return store.loadAsync().then(function() {
assert.strictEqual(store.get('port'), 78304)
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
})
})
t.test('with a valid UTF8 JSON file that contains no BOM', function() {
let store = new Nconf.File(noBomPath)
return store.loadAsync().then(function() {
assert.strictEqual(store.get('port'), 78304)
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
})
})
})
t.describe('#save()', function() {
let testPath = helpers.fixture('tmp.json')
let testSecondPath = helpers.fixture('tmp2.json')
t.test('should save the data correctly to original file specified', function() {
let store = new Nconf.File({file: testPath})
Object.keys(data).forEach(function (key) {
store.set(key, data[key])
})
assert.strictEqual(store.save(), store)
let readData = JSON.parse(fs.readFileSync(store.file))
assert.deepStrictEqual(readData, data)
})
t.test('should save the data to specified file', function() {
let store = new Nconf.File({file: testPath})
Object.keys(data).forEach(function (key) {
store.set(key, data[key])
})
store.save(testSecondPath)
let readData = JSON.parse(fs.readFileSync(testSecondPath))
assert.deepStrictEqual(readData, data)
})
t.after(function() {
return Promise.all([
fsPromise.unlink(testPath),
fsPromise.unlink(testSecondPath),
]).catch(function() {})
})
})
t.describe('#saveAsync()', function() {
let testPath = helpers.fixture('tmp.json')
let testSecondPath = helpers.fixture('tmp2.json')
t.test('should save the data correctly to original file specified', function() {
let store = new Nconf.File({file: testPath})
Object.keys(data).forEach(function (key) {
store.set(key, data[key])
})
return store.saveAsync().then(function(checkStore) {
assert.strictEqual(checkStore, store)
return fsPromise.readFile(store.file)
}).then(function(readData) {
assert.deepStrictEqual(JSON.parse(readData), data)
})
})
t.test('should save the data to specified file', function() {
let store = new Nconf.File({file: testPath})
Object.keys(data).forEach(function (key) {
store.set(key, data[key])
})
return store.saveAsync(testSecondPath).then(function() {
return fsPromise.readFile(testSecondPath)
}).then(function(readData) {
assert.deepStrictEqual(JSON.parse(readData), data)
})
})
t.after(function() {
return Promise.all([
fsPromise.unlink(testPath),
fsPromise.unlink(testSecondPath),
]).catch(function() {})
})
})
t.describe('#secure', function() {
t.test('the stringify() method should encrypt properly', function() {
let secureStore = new Nconf.File({
file: helpers.fixture('secure-iv.json'),
secure: 'super-secret-key-32-characterszz'
})
secureStore.store = data
let contents = JSON.parse(secureStore.stringify())
Object.keys(data).forEach(key => {
assert.strictEqual(typeof(contents[key]), 'object')
assert.strictEqual(typeof(contents[key].value), 'string')
assert.strictEqual(contents[key].alg, 'aes-256-ctr')
assert.strictEqual(typeof(contents[key].iv), 'string')
})
})
t.test('the parse() method should decrypt properly', function() {
let secureStore = new Nconf.File({
file: helpers.fixture('secure-iv.json'),
secure: 'super-secret-key-32-characterszz'
})
secureStore.store = data
let contents = secureStore.stringify()
let parsed = secureStore.parse(contents)
assert.deepStrictEqual(parsed, data)
})
t.test('the load() method should decrypt properly', function() {
let secureStore = new Nconf.File({
file: helpers.fixture('secure-iv.json'),
secure: 'super-secret-key-32-characterszz'
})
secureStore.load()
assert.deepStrictEqual(secureStore.store, data)
})
t.test('it should throw error on legacy encrypted files', function() {
let secureStore = new Nconf.File({
file: helpers.fixture('secure.json'),
secure: 'super-secretzzz'
})
assert.throws(function() {
secureStore.load()
}, /[Oo]utdated/)
})
})
/*
t.test('the search() method when the target file exists higher in the directory tree should update the file appropriately', function() {
let searchBase = require('os').homedir()
let filePath = path.join(searchBase, '.nconf')
fs.writeFileSync(filePath, JSON.stringify(data, null, 2))
let store = new Nconf.File({
file: '.nconf'
})
store.search(store.searchBase)
expect(store.file).toEqual(filePath)
fs.unlinkSync(filePath)
})
t.test('the search() method when the target file doesn't exist higher in the directory tree should update the file appropriately', function() {
let filePath = helpers.fixture('search-store.json')
let store = new Nconf.File({
dir: path.dirname(filePath),
file: 'search-store.json'
})
store.search()
expect(store.file).toEqual(filePath)
})
*/

View File

@ -1,31 +0,0 @@
/*
* literal-test.js: Tests for the nconf literal store.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/literal').addBatch({
"An instance of nconf.Literal": {
topic: new nconf.Literal({
foo: 'bar',
one: 2
}),
"should have the correct methods defined": function (literal) {
assert.equal(literal.type, 'literal');
assert.isFunction(literal.get);
assert.isFunction(literal.set);
assert.isFunction(literal.merge);
assert.isFunction(literal.loadSync);
},
"should have the correct values in the store": function (literal) {
assert.equal(literal.store.foo, 'bar');
assert.equal(literal.store.one, 2);
}
}
}).export(module);

View File

@ -0,0 +1,24 @@
import { Eltro as t, assert} from 'eltro'
import Nconf from '../../lib/nconf.mjs'
t.describe('nconf/stores/literal, An instance of nconf.Literal', function () {
var envOptions = {foo: 'bar', one: 2}
t.test("should have the correct methods defined", function () {
var literal = new Nconf.Literal()
assert.ok(literal.readOnly)
assert.strictEqual(literal.type, 'literal')
assert.strictEqual(typeof(literal.get), 'function')
assert.strictEqual(typeof(literal.set), 'function')
assert.strictEqual(typeof(literal.merge), 'function')
assert.strictEqual(typeof(literal.loadSync), 'function')
})
t.test("should have the correct values in the store", function () {
var literal = new Nconf.Literal(envOptions)
assert.strictEqual(literal.store.foo, 'bar')
assert.strictEqual(literal.store.one, 2)
assert.notOk(literal.set('foo', 'foo'))
assert.strictEqual(literal.store.foo, 'bar')
})
})

View File

@ -1,125 +0,0 @@
/*
* memory-store-test.js: Tests for the nconf Memory store.
*
* (C) 2011, Charlie Robbins and the Contributors.
*
*/
var vows = require('vows'),
assert = require('assert'),
nconf = require('../../lib/nconf'),
merge = require('../fixtures/data').merge;
vows.describe('nconf/stores/memory').addBatch({
"When using the nconf memory store": {
topic: new nconf.Memory(),
"the set() method": {
"should respond with true": function (store) {
assert.isTrue(store.set('foo:bar:bazz', 'buzz'));
assert.isTrue(store.set('falsy:number', 0));
assert.isTrue(store.set('falsy:string:empty', ''));
assert.isTrue(store.set('falsy:string:value', 'value'));
assert.isTrue(store.set('falsy:boolean', false));
assert.isTrue(store.set('falsy:object', null));
}
},
"the get() method": {
"should respond with the correct value": function (store) {
assert.equal(store.get('foo:bar:bazz'), 'buzz');
assert.equal(store.get('falsy:number'), 0);
assert.equal(store.get('falsy:string:empty'), '');
assert.equal(store.get('falsy:string:value'), 'value');
assert.equal(store.get('falsy:boolean'), false);
assert.equal(store.get('falsy:object'), null);
},
"should not fail when retrieving non-existent keys": {
"at the root level": function (store) {
assert.doesNotThrow(function() {
assert.equal(store.get('this:key:does:not:exist'), undefined);
}, TypeError);
},
"within numbers": function (store) {
assert.doesNotThrow(function() {
assert.equal(store.get('falsy:number:not:exist'), undefined);
}, TypeError);
},
"within booleans": function (store) {
assert.doesNotThrow(function() {
assert.equal(store.get('falsy:boolean:not:exist'), undefined);
}, TypeError);
},
"within objects": function (store) {
assert.doesNotThrow(function() {
assert.equal(store.get('falsy:object:not:exist'), undefined);
}, TypeError);
},
"within empty strings": function (store) {
assert.doesNotThrow(function() {
assert.equal(store.get('falsy:string:empty:not:exist'), undefined);
}, TypeError);
},
"within non-empty strings": function (store) {
assert.doesNotThrow(function() {
assert.equal(store.get('falsy:string:value:not:exist'), undefined);
}, TypeError);
}
}
},
"the clear() method": {
"should respond with the true": function (store) {
assert.equal(store.get('foo:bar:bazz'), 'buzz');
assert.isTrue(store.clear('foo:bar:bazz'));
assert.isTrue(typeof store.get('foo:bar:bazz') === 'undefined');
}
},
"the merge() method": {
"when overriding an existing literal value": function (store) {
store.set('merge:literal', 'string-value');
store.merge('merge:literal', merge);
assert.deepEqual(store.get('merge:literal'), merge);
},
"when overriding an existing Array value": function (store) {
store.set('merge:array', [1,2,3,4]);
store.merge('merge:array', merge);
assert.deepEqual(store.get('merge:literal'), merge);
},
"when merging into an existing Object value": function (store) {
store.set('merge:object', {
prop1: 2,
prop2: 'prop2',
prop3: {
bazz: 'bazz'
},
prop4: ['foo', 'bar']
});
store.merge('merge:object', merge);
assert.equal(store.get('merge:object:prop1'), 1);
assert.equal(store.get('merge:object:prop2').length, 3);
assert.deepEqual(store.get('merge:object:prop3'), {
foo: 'bar',
bar: 'foo',
bazz: 'bazz'
});
assert.equal(store.get('merge:object:prop4').length, 2);
}
}
},
"When using the nconf memory store with different logical separator": {
topic: new nconf.Memory({logicalSeparator: '||' }),
"when storing with : (colon)": {
"should store the config atomicly": function (store) {
store.set('foo:bar:bazz', 'buzz');
assert.isTrue(typeof store.get('foo:bar') === 'undefined');
assert.equal(store.get('foo:bar:bazz'), 'buzz');
}
},
"when storing with separator": {
"should be able to read the object": function (store) {
store.set('foo||bar||bazz', 'buzz');
assert.equal(store.get('foo||bar').bazz, 'buzz');
assert.equal(store.get('foo').bar.bazz, 'buzz');
}
}
}
}).export(module);

379
test/stores/memory.test.mjs Normal file
View File

@ -0,0 +1,379 @@
import { Eltro as t, assert} from 'eltro'
import Nconf from '../../lib/nconf.mjs'
import { merge } from '../fixtures/data.mjs'
t.describe('Memory Store', function () {
let store = new Nconf.Memory()
t.describe('#set()', function() {
t.test('should return true', function () {
assert.ok(store.set('foo:bar:bazz', 'buzz'))
assert.ok(store.set('falsy:number', 0))
assert.ok(store.set('falsy:string:empty', ''))
assert.ok(store.set('falsy:string:value', 'value'))
assert.ok(store.set('falsy:boolean', false))
assert.ok(store.set('falsy:object', null))
})
t.test('should support numbers as key', function() {
assert.notOk(store.get('523453'))
assert.ok(store.set(523453, true))
assert.ok(store.get('523453'))
})
t.test('should always make sure not store direct references to objects', function() {
const assertArray = [ 1, 2 ]
const assertObject = { a: 1 }
assert.ok(store.set('reference:test:arraydirect', assertArray))
assert.notStrictEqual(store.get('reference:test:arraydirect'), assertArray)
assert.ok(store.set('reference:test:objectdirect', assertObject))
assert.notStrictEqual(store.get('reference:test:objectdirect'), assertObject)
assert.ok(store.set('reference:test:objectchild', { x: assertArray, y: assertObject }))
assert.notStrictEqual(store.get('reference:test:objectchild:x'), assertArray)
assert.notStrictEqual(store.get('reference:test:objectchild:y'), assertObject)
})
t.test('should support numbers as key', function() {
let inStore = new Nconf.Memory()
inStore.set({ version: 1 })
assert.ok(inStore.set('version', 2))
assert.strictEqual(inStore.get('version'), 2)
inStore.readOnly = true
assert.notOk(inStore.set('version', 3))
assert.strictEqual(inStore.get('version'), 2)
})
t.test('should support numbers as key', function() {
let inStore = new Nconf.Memory({ parseValues: true })
inStore.set('test', '{"a":1}')
assert.ok(inStore.get('test'))
assert.deepStrictEqual(inStore.get('test'), { a: 1 })
inStore.set('test', 'undefined')
assert.notOk(inStore.get('test'))
})
t.test('should not do anything if given invalid set root', function() {
let inStore = new Nconf.Memory()
inStore.set({ version: 1 })
assert.deepStrictEqual(inStore.get(), { version: 1 })
assert.notOk(inStore.set(null))
assert.deepStrictEqual(inStore.get(), { version: 1 })
assert.notOk(inStore.set())
assert.deepStrictEqual(inStore.get(), { version: 1 })
assert.notOk(inStore.set([1, 2]))
assert.deepStrictEqual(inStore.get(), { version: 1 })
})
})
t.describe('#get()', function() {
t.test('should respond with the correct value', function () {
store.set('foo:bar:bazz', 'buzz')
store.set('falsy:number', 0)
store.set('falsy:string:empty', '')
store.set('falsy:string:value', 'value')
store.set('falsy:boolean', false)
store.set('falsy:object', null)
assert.strictEqual(store.get('foo:bar:bazz'), 'buzz')
assert.strictEqual(store.get('falsy:number'), 0)
assert.strictEqual(store.get('falsy:string:empty'), '')
assert.strictEqual(store.get('falsy:string:value'), 'value')
assert.strictEqual(store.get('falsy:boolean'), false)
assert.strictEqual(store.get('falsy:object'), null)
})
t.describe('should not at non-existent keys', function () {
t.test('at the root level', function () {
assert.strictEqual(store.get('this:key:does:not:exist'), undefined)
})
t.test('within numbers', function () {
assert.strictEqual(store.get('falsy:number:not:exist'), undefined)
})
t.test('within booleans', function () {
assert.strictEqual(store.get('falsy:boolean:not:exist'), undefined)
})
t.test('within objects', function () {
assert.strictEqual(store.get('falsy:object:not:exist'), undefined)
})
t.test('within empty strings', function () {
assert.strictEqual(store.get('falsy:string:empty:not:exist'), undefined)
})
t.test('within non-empty strings', function () {
assert.strictEqual(store.get('falsy:string:value:not:exist'), undefined)
})
})
})
t.describe('#clear()', function() {
t.test('should return false if readonly', function() {
let inStore = new Nconf.Memory()
inStore.set({ version: 1 })
inStore.readOnly = true
assert.notOk(inStore.clear('version'))
assert.strictEqual(inStore.get('version'), 1)
})
t.test('the clear() should return true if success', function () {
store.set('foo:bar:bazz', 'buzz')
assert.strictEqual(store.get('foo:bar:bazz'), 'buzz')
assert.ok(store.clear('foo:bar:bazz'))
assert.strictEqual(typeof store.get('foo:bar:bazz'), 'undefined')
})
t.test('should return false if not found', function() {
store.set('this:exists', 'fornow')
assert.strictEqual(store.get('this:exists'), 'fornow')
assert.notOk(store.clear('this:exists:but:not:this'))
assert.strictEqual(store.get('this:exists'), 'fornow')
})
})
t.describe('#merge()', function () {
t.test('should return false if readonly', function() {
let inStore = new Nconf.Memory()
inStore.set({ version: 1 })
inStore.readOnly = true
assert.notOk(inStore.merge({ version: 2 }))
assert.strictEqual(inStore.get('version'), 1)
})
t.test('when overriding an existing literal value', function () {
store.set('merge:literal', 'string-value')
store.merge('merge:literal', merge)
assert.deepStrictEqual(store.get('merge:literal'), merge)
})
t.test('when overriding an existing Array value', function () {
store.set('merge:array', [1, 2, 3, 4])
store.merge('merge:array', merge)
assert.deepStrictEqual(store.get('merge:literal'), merge)
})
t.test('when merging into an existing Object value', function () {
store.set('merge:object', {
prop1: 2,
prop2: 'prop2',
prop3: {
bazz: 'bazz'
},
prop4: ['foo', 'bar']
})
assert.strictEqual(store.get('merge:object:prop1'), 2)
assert.strictEqual(store.get('merge:object:prop2'), 'prop2')
assert.deepStrictEqual(store.get('merge:object:prop3'), {
bazz: 'bazz'
})
assert.strictEqual(store.get('merge:object:prop4').length, 2)
store.merge('merge:object', merge)
assert.strictEqual(store.get('merge:object:prop1'), 1)
assert.strictEqual(store.get('merge:object:prop2').length, 3)
assert.deepStrictEqual(store.get('merge:object:prop3'), {
foo: 'bar',
bar: 'foo',
bazz: 'bazz'
})
assert.strictEqual(store.get('merge:object:prop4').length, 2)
})
t.test('when merging at root level with an object', function() {
const assertFirst = 'herp'
const assertAfter = 'derp'
const newItem = { asdf: assertAfter }
let inStore = new Nconf.Memory()
inStore.set({
version: 1,
asdf: assertFirst,
})
assert.strictEqual(inStore.get('asdf'), assertFirst)
assert.strictEqual(inStore.get('version'), 1)
assert.ok(inStore.merge(newItem))
assert.notStrictEqual(inStore.get(), newItem)
assert.strictEqual(inStore.get('asdf'), assertAfter)
assert.strictEqual(inStore.get('version'), 1)
})
t.test('when merging at root level with an object with null after', function() {
const assertFirst = 'herp'
const assertAfter = 'derp'
const newItem = { asdf: assertAfter }
let inStore = new Nconf.Memory()
inStore.set({
version: 1,
asdf: assertFirst,
})
assert.strictEqual(inStore.get('asdf'), assertFirst)
assert.strictEqual(inStore.get('version'), 1)
assert.ok(inStore.merge(newItem), null)
assert.notStrictEqual(inStore.get(), newItem)
assert.strictEqual(inStore.get('asdf'), assertAfter)
assert.strictEqual(inStore.get('version'), 1)
})
t.test('when merging at root level with array', function() {
const newItem = 'herp'
let inStore = new Nconf.Memory()
inStore.set({
version: 1,
})
assert.strictEqual(inStore.get('version'), 1)
assert.notOk(inStore.get('0'))
assert.ok(inStore.merge([newItem]))
assert.ok(inStore.get('0'))
assert.strictEqual(inStore.get('0'), newItem)
assert.strictEqual(inStore.get('version'), 1)
})
t.test('when merging at root level with array and null after', function() {
const newItem = 'herp'
let inStore = new Nconf.Memory()
inStore.set({
version: 1,
})
assert.strictEqual(inStore.get('version'), 1)
assert.notOk(inStore.get('0'))
assert.ok(inStore.merge([newItem], null))
assert.ok(inStore.get('0'))
assert.strictEqual(inStore.get('0'), newItem)
assert.strictEqual(inStore.get('version'), 1)
})
t.test('it should always merge at root level if key is object regardless of what comes after', function() {
let inStore = new Nconf.Memory()
inStore.set({ herp: 1 })
assert.deepStrictEqual(inStore.get(), { herp: 1 })
assert.ok(inStore.merge({ version: 2 }, null))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2 })
assert.ok(inStore.merge({ test: 3 }, 1235))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3 })
assert.ok(inStore.merge({ foo: 4 }, 'sadfsadfs'))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4 })
assert.ok(inStore.merge({ bar: 5 }, { asdf: 1 }))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4, bar: 5 })
})
t.test('it should be robust about key type and overwriting', function() {
let inStore = new Nconf.Memory()
inStore.set({ herp: 1 })
assert.deepStrictEqual(inStore.get(), { herp: 1 })
assert.ok(inStore.merge(123, null))
assert.deepStrictEqual(inStore.get(), { herp: 1, '123': null })
assert.ok(inStore.merge(123, { a: 1 }))
assert.deepStrictEqual(inStore.get(), { herp: 1, '123': { a: 1 } })
assert.ok(inStore.merge(123, ['a', 1]))
assert.deepStrictEqual(inStore.get(), { herp: 1, '123': ['a', 1] })
})
t.test('it be able to handle basic value types with basic values', function() {
let inStore = new Nconf.Memory()
inStore.set({ herp: 1 })
assert.deepStrictEqual(inStore.get(), { herp: 1 })
assert.ok(inStore.merge({ version: 2 }, null))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2 })
assert.ok(inStore.merge({ test: 3 }, 1235))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3 })
assert.ok(inStore.merge({ foo: 4 }, 'sadfsadfs'))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4 })
assert.ok(inStore.merge({ bar: 5 }, { asdf: 1 }))
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4, bar: 5 })
})
t.test('when sending a single path with null, string or number value should overwrite', function() {
const assertString = 'Beginning'
const assertNumber = 358792
let inStore = new Nconf.Memory()
inStore.set({
herp: {
derp: 123,
},
})
assert.strictEqual(inStore.get('herp:derp'), 123)
assert.ok(inStore.merge('herp:derp', null))
assert.strictEqual(inStore.get('herp:derp'), null)
assert.ok(inStore.merge('herp:derp', assertString))
assert.strictEqual(inStore.get('herp:derp'), assertString)
assert.ok(inStore.merge('herp:derp', assertNumber))
assert.strictEqual(inStore.get('herp:derp'), assertNumber)
})
t.test('when merging at nonexisting path', function() {
const assertNewItem = { a: 1, b: 2 }
const assertPath = 'new:path:for:item'
let inStore = new Nconf.Memory()
inStore.set({
version: 1,
})
assert.strictEqual(inStore.get('version'), 1)
assert.notOk(inStore.get(assertPath))
assert.ok(inStore.merge(assertPath, assertNewItem))
assert.ok(inStore.get(assertPath))
assert.notStrictEqual(inStore.get(assertPath), assertNewItem)
assert.deepStrictEqual(inStore.get(assertPath), assertNewItem)
assert.deepStrictEqual(inStore.get().new.path.for.item, assertNewItem)
})
})
t.describe('#reset()', function() {
t.test('should remove everything', function() {
const assertRoot = {
version: 1,
asdf: 'test',
}
let inStore = new Nconf.Memory()
inStore.set(assertRoot)
assert.deepStrictEqual(inStore.get(), assertRoot)
inStore.reset()
assert.deepStrictEqual(inStore.get(), {})
})
t.test('should do nothing if readonly', function() {
const assertRoot = {
version: 1,
asdf: 'test',
}
let inStore = new Nconf.Memory()
inStore.set(assertRoot)
assert.deepStrictEqual(inStore.get(), assertRoot)
inStore.readOnly = true
assert.notOk(inStore.reset())
assert.deepStrictEqual(inStore.get(), assertRoot)
})
})
t.describe('options', function () {
t.describe('logicalSeparator', function () {
var store = new Nconf.Memory({logicalSeparator: '||'})
t.test('when storing with : (colon), should store the config atomicly', function () {
store.set('foo:bar:bazz', 'buzz')
assert.strictEqual(typeof store.get('foo:bar'), 'undefined')
assert.strictEqual(store.get('foo:bar:bazz'), 'buzz')
})
t.test('when storing with separator, should be able to read the object', function () {
store.set('foo||bar||bazz', 'buzz')
assert.strictEqual(store.get('foo||bar').bazz, 'buzz')
assert.strictEqual(store.get('foo').bar.bazz, 'buzz')
})
})
t.test('should allow specifying readonly', function () {
var store = new Nconf.Memory({ readOnly: true })
assert.strictEqual(store.readOnly, true)
})
})
})

View File

@ -1,50 +0,0 @@
var fs = require('fs'),
path = require('path'),
nconf = require('./lib/nconf');
//
// Configure the provider with a single store and
// support for command-line arguments and environment
// variables.
//
var single = new nconf.Provider({
env: true,
argv: true,
store: {
type: 'file',
file: path.join(__dirname, 'config.json')
}
});
//
// Configure the provider with multiple hierarchical stores
// representing `user` and `global` configuration values.
//
var multiple = new nconf.Provider({
stores: [
{ name: 'user', type: 'file', file: path.join(__dirname, 'user-config.json') },
{ name: 'global', type: 'global', file: path.join(__dirname, 'global-config.json') }
]
});
//
// Setup nconf to use the 'file' store and set a couple of values;
//
nconf.use('file', { file: path.join(__dirname, 'config.json') });
nconf.set('database:host', '127.0.0.1');
nconf.set('database:port', 5984);
//
// Get the entire database object from nconf
//
var database = nconf.get('database');
console.dir(database);
//
// Save the configuration object to disk
//
nconf.save(function (err) {
fs.readFile(path.join(__dirname, 'config.json'), function (err, data) {
console.dir(JSON.parse(data.toString()))
});
});