123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- 'use strict';
- const fs = require('fs');
- const path = require('path');
- const crypto = require('crypto');
- const assert = require('assert');
- const EventEmitter = require('events');
- const dotProp = require('dot-prop');
- const makeDir = require('make-dir');
- const pkgUp = require('pkg-up');
- const envPaths = require('env-paths');
- const writeFileAtomic = require('write-file-atomic');
-
- const obj = () => Object.create(null);
-
- // Prevent caching of this module so module.parent is always accurate
- delete require.cache[__filename];
- const parentDir = path.dirname((module.parent && module.parent.filename) || '.');
-
- class Conf {
- constructor(opts) {
- const pkgPath = pkgUp.sync(parentDir);
-
- opts = Object.assign({
- // Can't use `require` because of Webpack being annoying:
- // https://github.com/webpack/webpack/issues/196
- projectName: pkgPath && JSON.parse(fs.readFileSync(pkgPath, 'utf8')).name
- }, opts);
-
- if (!opts.projectName && !opts.cwd) {
- throw new Error('Project name could not be inferred. Please specify the `projectName` option.');
- }
-
- opts = Object.assign({
- configName: 'config'
- }, opts);
-
- if (!opts.cwd) {
- opts.cwd = envPaths(opts.projectName).config;
- }
-
- this.events = new EventEmitter();
- this.encryptionKey = opts.encryptionKey;
- this.path = path.resolve(opts.cwd, `${opts.configName}.json`);
- this.store = Object.assign(obj(), opts.defaults, this.store);
- }
- get(key, defaultValue) {
- return dotProp.get(this.store, key, defaultValue);
- }
- set(key, val) {
- if (typeof key !== 'string' && typeof key !== 'object') {
- throw new TypeError(`Expected \`key\` to be of type \`string\` or \`object\`, got ${typeof key}`);
- }
-
- const store = this.store;
-
- if (typeof key === 'object') {
- for (const k of Object.keys(key)) {
- dotProp.set(store, k, key[k]);
- }
- } else {
- dotProp.set(store, key, val);
- }
-
- this.store = store;
- }
- has(key) {
- return dotProp.has(this.store, key);
- }
- delete(key) {
- const store = this.store;
- dotProp.delete(store, key);
- this.store = store;
- }
- clear() {
- this.store = obj();
- }
- onDidChange(key, callback) {
- if (typeof key !== 'string') {
- throw new TypeError(`Expected \`key\` to be of type \`string\`, got ${typeof key}`);
- }
-
- if (typeof callback !== 'function') {
- throw new TypeError(`Expected \`callback\` to be of type \`function\`, got ${typeof callback}`);
- }
-
- let currentValue = this.get(key);
-
- const onChange = () => {
- const oldValue = currentValue;
- const newValue = this.get(key);
-
- try {
- assert.deepEqual(newValue, oldValue);
- } catch (err) {
- currentValue = newValue;
- callback.call(this, newValue, oldValue);
- }
- };
-
- this.events.on('change', onChange);
- return () => this.events.removeListener('change', onChange);
- }
- get size() {
- return Object.keys(this.store).length;
- }
- get store() {
- try {
- let data = fs.readFileSync(this.path, this.encryptionKey ? null : 'utf8');
-
- if (this.encryptionKey) {
- try {
- const decipher = crypto.createDecipher('aes-256-cbc', this.encryptionKey);
- data = Buffer.concat([decipher.update(data), decipher.final()]);
- } catch (err) {/* ignore */}
- }
-
- return Object.assign(obj(), JSON.parse(data));
- } catch (err) {
- if (err.code === 'ENOENT') {
- makeDir.sync(path.dirname(this.path));
- return obj();
- }
-
- if (err.name === 'SyntaxError') {
- return obj();
- }
-
- throw err;
- }
- }
- set store(val) {
- // Ensure the directory exists as it could have been deleted in the meantime
- makeDir.sync(path.dirname(this.path));
-
- let data = JSON.stringify(val, null, '\t');
-
- if (this.encryptionKey) {
- const cipher = crypto.createCipher('aes-256-cbc', this.encryptionKey);
- data = Buffer.concat([cipher.update(Buffer.from(data)), cipher.final()]);
- }
-
- writeFileAtomic.sync(this.path, data);
- this.events.emit('change');
- }
- // TODO: Use `Object.entries()` here at some point
- * [Symbol.iterator]() {
- const store = this.store;
-
- for (const key of Object.keys(store)) {
- yield [key, store[key]];
- }
- }
- }
-
- module.exports = Conf;
|