├── .gitignore
├── .gitattributes
├── .travis.yml
├── .editorconfig
├── package.json
├── readme.md
├── index.js
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - '6'
5 | - '4'
6 | - '0.12'
7 | - '0.10'
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [{package.json,*.yml}]
11 | indent_style = space
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "configstore",
3 | "version": "2.0.0",
4 | "description": "Easily load and save config without having to think about where and how",
5 | "license": "BSD-2-Clause",
6 | "repository": "yeoman/configstore",
7 | "author": {
8 | "name": "Sindre Sorhus",
9 | "email": "sindresorhus@gmail.com",
10 | "url": "sindresorhus.com"
11 | },
12 | "engines": {
13 | "node": ">=0.10.0"
14 | },
15 | "scripts": {
16 | "test": "xo && ava"
17 | },
18 | "files": [
19 | "index.js"
20 | ],
21 | "keywords": [
22 | "config",
23 | "store",
24 | "storage",
25 | "conf",
26 | "configuration",
27 | "settings",
28 | "preferences",
29 | "json",
30 | "data",
31 | "persist",
32 | "persistent",
33 | "save"
34 | ],
35 | "dependencies": {
36 | "dot-prop": "^3.0.0",
37 | "graceful-fs": "^4.1.2",
38 | "mkdirp": "^0.5.0",
39 | "object-assign": "^4.0.1",
40 | "os-tmpdir": "^1.0.0",
41 | "osenv": "^0.1.0",
42 | "uuid": "^2.0.1",
43 | "write-file-atomic": "^1.1.2",
44 | "xdg-basedir": "^2.0.0"
45 | },
46 | "devDependencies": {
47 | "ava": "*",
48 | "path-exists": "^2.0.0",
49 | "xo": "*"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # configstore [](https://travis-ci.org/yeoman/configstore)
2 |
3 | > Easily load and persist config without having to think about where and how
4 |
5 | Config is stored in a JSON file located in `$XDG_CONFIG_HOME` or `~/.config`.
6 | Example: `~/.config/configstore/some-id.json`
7 |
8 |
9 | ## Usage
10 |
11 | ```js
12 | const Configstore = require('configstore');
13 | const pkg = require('./package.json');
14 |
15 | // create a Configstore instance with an unique ID e.g.
16 | // package name and optionally some default values
17 | const conf = new Configstore(pkg.name, {foo: 'bar'});
18 |
19 | console.log(conf.get('foo'));
20 | //=> 'bar'
21 |
22 | conf.set('awesome', true);
23 | console.log(conf.get('awesome'));
24 | //=> true
25 |
26 | // use dot-notation to access nested properties
27 | conf.set('bar.baz', true);
28 | console.log(conf.get('bar'));
29 | //=> {baz: true}
30 |
31 | conf.delete('awesome');
32 | console.log(conf.get('awesome'));
33 | //=> undefined
34 | ```
35 |
36 |
37 | ## API
38 |
39 | ### Configstore(packageName, [defaults], [options])
40 |
41 | Returns a new instance.
42 |
43 | #### packageName
44 |
45 | Type: `string`
46 |
47 | Name of your package.
48 |
49 | #### defaults
50 |
51 | Type: `Object`
52 |
53 | Default config.
54 |
55 | #### options
56 |
57 | ##### globalConfigPath
58 |
59 | Type: `boolean`
60 | Default: `false`
61 |
62 | Store the config at `$CONFIG/package-name/config.json` instead of the default `$CONFIG/configstore/package-name.json`. This is not recommended as you might end up conflicting with other tools, rendering the "without having to think" idea moot.
63 |
64 | ### Instance
65 |
66 | You can use [dot-notation](https://github.com/sindresorhus/dot-prop) in a `key` to access nested properties.
67 |
68 | ### .set(key, value)
69 |
70 | Set an item.
71 |
72 | ### .set(object)
73 |
74 | Set multiple items at once.
75 |
76 | ### .get(key)
77 |
78 | Get an item.
79 |
80 | ### .has(key)
81 |
82 | Check if an item exists.
83 |
84 | ### .delete(key)
85 |
86 | Delete an item.
87 |
88 | ### .clear()
89 |
90 | Delete all items.
91 |
92 | ### .size
93 |
94 | Get the item count.
95 |
96 | ### .path
97 |
98 | Get the path to the config file. Can be used to show the user where the config file is located or even better open it for them.
99 |
100 | ### .all
101 |
102 | Get all the config as an object or replace the current config with an object:
103 |
104 | ```js
105 | conf.all = {
106 | hello: 'world'
107 | };
108 | ```
109 |
110 |
111 | ## License
112 |
113 | [BSD license](http://opensource.org/licenses/bsd-license.php)
114 | Copyright Google
115 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var path = require('path');
3 | var fs = require('graceful-fs');
4 | var osenv = require('osenv');
5 | var assign = require('object-assign');
6 | var mkdirp = require('mkdirp');
7 | var uuid = require('uuid');
8 | var xdgBasedir = require('xdg-basedir');
9 | var osTmpdir = require('os-tmpdir');
10 | var writeFileAtomic = require('write-file-atomic');
11 | var dotProp = require('dot-prop');
12 |
13 | var user = (osenv.user() || uuid.v4()).replace(/\\/g, '');
14 | var configDir = xdgBasedir.config || path.join(osTmpdir(), user, '.config');
15 | var permissionError = 'You don\'t have access to this file.';
16 | var defaultPathMode = parseInt('0700', 8);
17 | var writeFileOptions = {mode: parseInt('0600', 8)};
18 |
19 | function Configstore(id, defaults, opts) {
20 | opts = opts || {};
21 |
22 | var pathPrefix = opts.globalConfigPath ?
23 | path.join(id, 'config.json') :
24 | path.join('configstore', id + '.json');
25 |
26 | this.path = opts.configPath || path.join(configDir, pathPrefix);
27 |
28 | this.all = assign({}, defaults || {}, this.all || {});
29 | }
30 |
31 | Configstore.prototype = Object.create(Object.prototype, {
32 | all: {
33 | get: function () {
34 | try {
35 | return JSON.parse(fs.readFileSync(this.path, 'utf8'));
36 | } catch (err) {
37 | // create dir if it doesn't exist
38 | if (err.code === 'ENOENT') {
39 | mkdirp.sync(path.dirname(this.path), defaultPathMode);
40 | return {};
41 | }
42 |
43 | // improve the message of permission errors
44 | if (err.code === 'EACCES') {
45 | err.message = err.message + '\n' + permissionError + '\n';
46 | }
47 |
48 | // empty the file if it encounters invalid JSON
49 | if (err.name === 'SyntaxError') {
50 | writeFileAtomic.sync(this.path, '', writeFileOptions);
51 | return {};
52 | }
53 |
54 | throw err;
55 | }
56 | },
57 | set: function (val) {
58 | try {
59 | // make sure the folder exists as it
60 | // could have been deleted in the meantime
61 | mkdirp.sync(path.dirname(this.path), defaultPathMode);
62 |
63 | writeFileAtomic.sync(this.path, JSON.stringify(val, null, '\t'), writeFileOptions);
64 | } catch (err) {
65 | // improve the message of permission errors
66 | if (err.code === 'EACCES') {
67 | err.message = err.message + '\n' + permissionError + '\n';
68 | }
69 |
70 | throw err;
71 | }
72 | }
73 | },
74 | size: {
75 | get: function () {
76 | return Object.keys(this.all || {}).length;
77 | }
78 | }
79 | });
80 |
81 | Configstore.prototype.get = function (key) {
82 | return dotProp.get(this.all, key);
83 | };
84 |
85 | Configstore.prototype.set = function (key, val) {
86 | var config = this.all;
87 | if (arguments.length === 1) {
88 | Object.keys(key).forEach(function (k) {
89 | dotProp.set(config, k, key[k]);
90 | });
91 | } else {
92 | dotProp.set(config, key, val);
93 | }
94 | this.all = config;
95 | };
96 |
97 | Configstore.prototype.has = function (key) {
98 | return dotProp.has(this.all, key);
99 | };
100 |
101 | Configstore.prototype.delete = Configstore.prototype.del = function (key) {
102 | var config = this.all;
103 | dotProp.delete(config, key);
104 | this.all = config;
105 | };
106 |
107 | Configstore.prototype.clear = function () {
108 | this.all = {};
109 | };
110 |
111 | module.exports = Configstore;
112 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import {serial as test} from 'ava';
3 | import pathExists from 'path-exists';
4 | import Configstore from './';
5 |
6 | const configstorePath = new Configstore('configstore-test').path;
7 |
8 | test.beforeEach(t => {
9 | fs.unlinkSync(configstorePath);
10 | t.context.conf = new Configstore('configstore-test');
11 | });
12 |
13 | test('.set() and .get()', t => {
14 | t.context.conf.set('foo', 'bar');
15 | t.context.conf.set('baz.boo', true);
16 | t.is(t.context.conf.get('foo'), 'bar');
17 | t.is(t.context.conf.get('baz.boo'), true);
18 | });
19 |
20 | test('.set() with object and .get()', t => {
21 | t.context.conf.set({
22 | foo1: 'bar1',
23 | foo2: 'bar2',
24 | baz: {
25 | boo: 'foo',
26 | foo: {
27 | bar: 'baz'
28 | }
29 | }
30 | });
31 | t.is(t.context.conf.get('foo1'), 'bar1');
32 | t.is(t.context.conf.get('foo2'), 'bar2');
33 | t.deepEqual(t.context.conf.get('baz'), {boo: 'foo', foo: {bar: 'baz'}});
34 | t.is(t.context.conf.get('baz.boo'), 'foo');
35 | t.deepEqual(t.context.conf.get('baz.foo'), {bar: 'baz'});
36 | t.is(t.context.conf.get('baz.foo.bar'), 'baz');
37 | });
38 |
39 | test('.has()', t => {
40 | t.context.conf.set('foo', '🦄');
41 | t.context.conf.set('baz.boo', '🦄');
42 | t.true(t.context.conf.has('foo'));
43 | t.true(t.context.conf.has('baz.boo'));
44 | t.false(t.context.conf.has('missing'));
45 | });
46 |
47 | test('.del() - SOFT-DEPRECATED', t => {
48 | t.context.conf.set('foo', 'bar');
49 | t.context.conf.del('foo');
50 | t.not(t.context.conf.get('foo'), 'bar');
51 | });
52 |
53 | test('.delete()', t => {
54 | t.context.conf.set('foo', 'bar');
55 | t.context.conf.set('baz.boo', true);
56 | t.context.conf.set('baz.foo.bar', 'baz');
57 | t.context.conf.delete('foo');
58 | t.not(t.context.conf.get('foo'), 'bar');
59 | t.context.conf.delete('baz.boo');
60 | t.not(t.context.conf.get('baz.boo'), true);
61 | t.context.conf.delete('baz.foo');
62 | t.not(t.context.conf.get('baz.foo'), {bar: 'baz'});
63 | t.context.conf.set('foo.bar.baz', {awesome: 'icecream'});
64 | t.context.conf.set('foo.bar.zoo', {awesome: 'redpanda'});
65 | t.context.conf.delete('foo.bar.baz');
66 | t.is(t.context.conf.get('foo.bar.zoo.awesome'), 'redpanda');
67 | });
68 |
69 | test('.clear()', t => {
70 | t.context.conf.set('foo', 'bar');
71 | t.context.conf.set('foo1', 'bar1');
72 | t.context.conf.set('baz.boo', true);
73 | t.context.conf.clear();
74 | t.is(t.context.conf.size, 0);
75 | });
76 |
77 | test('.all', t => {
78 | t.context.conf.set('foo', 'bar');
79 | t.context.conf.set('baz.boo', true);
80 | t.is(t.context.conf.all.foo, 'bar');
81 | t.deepEqual(t.context.conf.all.baz, {boo: true});
82 | });
83 |
84 | test('.size', t => {
85 | t.context.conf.set('foo', 'bar');
86 | t.is(t.context.conf.size, 1);
87 | });
88 |
89 | test('.path', t => {
90 | t.context.conf.set('foo', 'bar');
91 | t.true(pathExists.sync(t.context.conf.path));
92 | });
93 |
94 | test('use default value', t => {
95 | const conf = new Configstore('configstore-test', {foo: 'bar'});
96 | t.is(conf.get('foo'), 'bar');
97 | });
98 |
99 | test('support global namespace path option', t => {
100 | const conf = new Configstore('configstore-test', {}, {globalConfigPath: true});
101 | const regex = /configstore-test(\/|\\)config.json$/;
102 | t.true(regex.test(conf.path));
103 | });
104 |
105 | test('ensure `.all` is always an object', t => {
106 | fs.unlinkSync(configstorePath);
107 | t.notThrows(() => t.context.conf.get('foo'));
108 | });
109 |
--------------------------------------------------------------------------------