electron-settings
Advanced tools
Comparing version 2.2.4 to 3.0.0
19
index.js
/** | ||
* Electron Settings | ||
* Electron Settings - User settings manager for Electron. | ||
* | ||
* A simple persistent user settings manager for Electron. Originally adapted | ||
* from Atom's own configuration manager, electron-settings allows you to save | ||
* user settings to the disk so that they can be loaded in the next time your | ||
* app starts up. | ||
* | ||
* NOTE: v2 is not compatible with earlier versions of electron-settings. | ||
* | ||
* @version 2.2.2 | ||
* @version 3.0.0 | ||
* @author Nathan Buchar | ||
* @copyright 2016 Nathan Buchar <hello@nathanbuchar.com> | ||
* @copyright 2016-2017 Nathan Buchar <hello@nathanbuchar.com> | ||
* @license ISC | ||
*/ | ||
module.exports = require('./lib/settings'); | ||
'use strict'; | ||
const Settings = require('./lib/settings'); | ||
module.exports = new Settings(); |
'use strict'; | ||
const assert = require('assert'); | ||
const debug = require('debug')('electron-settings:main'); | ||
const deepExtend = require('deep-extend'); | ||
const clone = require('clone'); | ||
const electron = require('electron'); | ||
const exists = require('file-exists'); | ||
const events = require('events'); | ||
const fs = require('fs-extra'); | ||
const helpers = require('key-path-helpers'); | ||
const path = require('path'); | ||
const { EventEmitter } = require('events'); | ||
const Observer = require('./observer'); | ||
const Helpers = require('./settings-helpers'); | ||
const Observer = require('./settings-observer'); | ||
/** | ||
* Obtain a reference to the Electron app. If electron-settings is required | ||
* within the context of a renderer view, we need to import it via remote. | ||
* | ||
* @see http://electron.atom.io/docs/api/app | ||
* @type {Object} | ||
*/ | ||
const app = electron.app || electron.remote.app; | ||
class Settings extends events.EventEmitter { | ||
/** | ||
* The user data path for the Electron app. | ||
* | ||
* @see http://electron.atom.io/docs/api/app/#appgetpathname | ||
* @type {string} | ||
*/ | ||
const USER_DATA_PATH = app.getPath('userData'); | ||
/** | ||
* The default settings file name. | ||
* | ||
* @type {string} | ||
*/ | ||
const SETTINGS_FILE_NAME = 'Settings'; | ||
/** | ||
* The Settings class. | ||
* | ||
* @extends events.EventEmitter | ||
*/ | ||
class Settings extends EventEmitter { | ||
constructor() { | ||
@@ -51,3 +18,3 @@ super(); | ||
/** | ||
* The default settings Object. | ||
* Local reference to the Electron app. | ||
* | ||
@@ -57,82 +24,52 @@ * @type {Object} | ||
*/ | ||
this._defaults = {}; | ||
this._app = electron.app || electron.remote.app; | ||
/** | ||
* The FSWatcher instance. This will watch if the settings file changes | ||
* and notify key path observers. | ||
* The path to the user data folder for the current app. | ||
* | ||
* @type {FSWatcher} | ||
* @default null | ||
* @type {string} | ||
* @private | ||
*/ | ||
this._fsWatcher = null; | ||
this._userDataPath = this._app.getPath('userData'); | ||
/** | ||
* Called when the settings file has been created for the first time. | ||
* The absolute path to the settings file. | ||
* | ||
* @type {Function} | ||
* @type {string} | ||
* @private | ||
*/ | ||
this._handleSettingsCreate = this._onSettingsCreate.bind(this); | ||
this._settingsPath = path.join(this._userDataPath, 'Settings'); | ||
/** | ||
* Called when the file has been changed. | ||
* The FSWatcher instance. This will watch if the settings file and | ||
* notify key path observers. | ||
* | ||
* @type {Function} | ||
* @type {FSWatcher} | ||
* @default null | ||
* @private | ||
*/ | ||
this._handleFileChange = this._onFileChange.bind(this); | ||
this._fsWatcher = null; | ||
this._init(); | ||
/** | ||
* Called when the settings file is changed or renamed. | ||
* | ||
* @type {Object} | ||
* @private | ||
*/ | ||
this._handleSettingsChange = this._onSettingsChange.bind(this); | ||
} | ||
/** | ||
* Initializes the Settings instance. | ||
* Watches the settings file for changes using the native `FSWatcher` | ||
* class in case the settings file is changed outside of | ||
* ElectronSettings' jursidiction. | ||
* | ||
* @private | ||
*/ | ||
_init() { | ||
this._registerEvents(); | ||
this._observeSettingsFile(); | ||
} | ||
/** | ||
* Registers internal event handlers. | ||
* | ||
* @private | ||
*/ | ||
_registerEvents() { | ||
this.addListener(Settings.Events.CREATE, this._handleSettingsCreate); | ||
} | ||
/** | ||
* Observes the settings file for changes. It is possible that the settings | ||
* file may be changed from a system outside of electron-settings' control or | ||
* possible a separate electron-settings instance. If this happens, it would | ||
* be nice to trigger key path changes. | ||
* | ||
* @see https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener | ||
* @param {boolean} [reset=false] Reset the FSWatcher if it already exists. | ||
* @returns {boolean} | ||
* @private | ||
*/ | ||
_observeSettingsFile(reset=false) { | ||
const pathToSettings = this.getSettingsFilePath(); | ||
// Close the FSWatcher and nullify its reference. | ||
if (reset && this._fsWatcher) { | ||
this._fsWatcher.close(); | ||
this._fsWatcher = null; | ||
debug('settings file observer reset'); | ||
} | ||
_watchSettings() { | ||
if (!this._fsWatcher) { | ||
try { | ||
this._fsWatcher = fs.watch(pathToSettings, this._handleFileChange); | ||
debug('observing settings file'); | ||
} catch (e) { | ||
// File may not yet exist or possible user permissions error. | ||
debug('could not observe settings file at this time'); | ||
this._fsWatcher = fs.watch(this._settingsPath, this._handleSettingsChange); | ||
} catch(err) { | ||
// File may not exist yet or possible user permissions error. | ||
} | ||
@@ -143,61 +80,16 @@ } | ||
/** | ||
* Configures electron-settings global default options. | ||
* Writes the given settings object to the disk. | ||
* | ||
* @param {Object} options | ||
* @param {Object} [obj={}] | ||
* @param {Object} [opts={}] | ||
* @private | ||
*/ | ||
_configureGlobalSettings(options) { | ||
const opts = this._extendDefaultOptions(options); | ||
Settings.DefaultOptions = opts; | ||
debug(`global configuration set to ${JSON.stringify(opts)}`); | ||
} | ||
/** | ||
* Parses save options and ensures that default values are set if they are | ||
* not provided. | ||
* | ||
* @param {Object} [options] | ||
* @param {boolean} [options.atomicSaving=true] | ||
* @param {boolean} [options.prettify=false] | ||
* @param {boolean} [options.overwrite=false] | ||
* @param {string} [options.settingsDir=USER_DATA_PATH] | ||
* @param {string} [options.settingsFileName=Settings] | ||
* @returns {Object} | ||
* @private | ||
*/ | ||
_extendDefaultOptions(options={}) { | ||
return Object.assign({}, Settings.DefaultOptions, options); | ||
} | ||
/** | ||
* Sets electron-settings default settings. These will be applied upon | ||
* settings file creation, as well as `applyDefaults()` and | ||
* `resetToDefaults()`. | ||
* | ||
* @param {Object} obj | ||
* @private | ||
*/ | ||
_setDefaults(obj) { | ||
this._defaults = clone(obj); | ||
debug(`defaults set to ${JSON.stringify(this._defaults)}`); | ||
} | ||
/** | ||
* Deletes the settings file. This may occur if the data has become corrupted | ||
* and can no longer be read. | ||
* | ||
* @private | ||
*/ | ||
_unlinkSettingsFileSync() { | ||
const pathToSettings = this.getSettingsFilePath(); | ||
_writeSettings(obj={}, opts={}) { | ||
try { | ||
fs.unlinkSync(pathToSettings); | ||
debug(`settings file deleted at ${pathToSettings}`); | ||
} catch (e) { | ||
// Do nothing. | ||
fs.outputJsonSync(this._settingsPath, obj, { | ||
spaces: opts.prettify ? 2 : 0 | ||
}); | ||
this._watchSettings(); | ||
} catch(err) { | ||
// Something went wrong. | ||
} | ||
@@ -207,106 +99,14 @@ } | ||
/** | ||
* Deletes the settings file and re-ensures its existence with default | ||
* settings if possible. This is the doomsday scenario. | ||
* Returns the settings from the settings file, or creates the file | ||
* if it does not yet exist. | ||
* | ||
* @private | ||
*/ | ||
_resetSettingsFileSync() { | ||
debug('resetting settings file...'); | ||
this._unlinkSettingsFileSync(); | ||
this._ensureSettingsFileSync(); | ||
} | ||
/** | ||
* Checks if the settings file exists on the disk. If it does not, it is | ||
* created with an empty object as its contents. | ||
* | ||
* @returns {Promise} | ||
* @private | ||
*/ | ||
_ensureSettingsFile() { | ||
debug('ensuring settings file...'); | ||
return new Promise((resolve, reject) => { | ||
if (!this.settingsFileExists()) { | ||
const defaults = this._defaults; | ||
this._writeSettingsFile(defaults).then(() => { | ||
this._emitCreateEvents(); | ||
resolve(); | ||
}, reject); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
} | ||
/** | ||
* The synchronous version of `_ensureSettingsFile()`. | ||
* | ||
* @see _ensureSettingsFile | ||
* @private | ||
*/ | ||
_ensureSettingsFileSync() { | ||
debug('ensuring settings file...'); | ||
if (!this.settingsFileExists()) { | ||
const defaults = this._defaults; | ||
this._writeSettingsFileSync(defaults); | ||
this._emitCreateEvents(); | ||
} | ||
} | ||
/** | ||
* Reads the settings file from the disk and parses the contents as JSON. | ||
* | ||
* @returns {Promise} | ||
* @private | ||
*/ | ||
_readSettingsFile() { | ||
return new Promise((resolve, reject) => { | ||
this._ensureSettingsFile().then(() => { | ||
const pathToSettings = this.getSettingsFilePath(); | ||
debug('reading settings...'); | ||
fs.readJson(pathToSettings, (err, obj) => { | ||
if (err) { | ||
debug(`ERROR: malformed JSON detected at ${pathToSettings}`); | ||
this._resetSettingsFileSync(); | ||
this._readSettingsFile().then(resolve, reject); | ||
} else { | ||
resolve(obj); | ||
} | ||
}); | ||
}, reject); | ||
}); | ||
} | ||
/** | ||
* The synchronous version of `_readSettingsFile()`. | ||
* | ||
* @see _readSettingsFile | ||
* @returns {Object} | ||
* @private | ||
*/ | ||
_readSettingsFileSync() { | ||
this._ensureSettingsFileSync(); | ||
const pathToSettings = this.getSettingsFilePath(); | ||
_readSettings() { | ||
try { | ||
debug('reading settings...'); | ||
const obj = fs.readJsonSync(pathToSettings); | ||
return obj; | ||
} catch (e) { | ||
debug(`ERROR: malformed JSON detected at ${pathToSettings}`); | ||
this._resetSettingsFileSync(); | ||
return this._readSettingsFileSync(); | ||
return fs.readJsonSync(this._settingsPath); | ||
} catch(err) { | ||
this._writeSettings(); | ||
return {}; | ||
} | ||
@@ -316,169 +116,48 @@ } | ||
/** | ||
* Parses the given object to a JSON string and saves it to the disk. If | ||
* atomic saving is enabled, then we firt save a temp file, and once it has | ||
* been successfully written, we overwrite the old settings file. | ||
* Called when the settings file has been changed or | ||
* renamed (moved/deleted). | ||
* | ||
* @param {Object} obj | ||
* @param {Object} [options] | ||
* @returns {Promise} | ||
* @type {string} eventType | ||
* @private | ||
*/ | ||
_writeSettingsFile(obj, options) { | ||
debug('writing settings file...'); | ||
const opts = this._extendDefaultOptions(options); | ||
const pathToSettings = this.getSettingsFilePath(); | ||
const spaces = opts.prettify ? 2 : 0; | ||
return new Promise((resolve, reject) => { | ||
if (opts.atomicSaving) { | ||
const tmpFilePath = `${pathToSettings}-tmp`; | ||
fs.outputJson(tmpFilePath, obj, { spaces }, err => { | ||
if (!err) { | ||
fs.rename(tmpFilePath, pathToSettings, err => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
this._observeSettingsFile(true); | ||
this._emitWriteEvents(); | ||
resolve(); | ||
} | ||
}); | ||
} else { | ||
fs.unlink(tmpFilePath, () => { | ||
this._resetSettingsFileSync(); | ||
reject(err); | ||
}); | ||
} | ||
}); | ||
} else { | ||
fs.outputJson(pathToSettings, obj, { spaces }, err => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
this._observeSettingsFile(true); | ||
this._emitWriteEvents(); | ||
resolve(); | ||
} | ||
}); | ||
_onSettingsChange(eventType) { | ||
switch (eventType) { | ||
case 'change': { | ||
this.emit(Settings.Events.CHANGE); | ||
break; | ||
} | ||
}); | ||
} | ||
/** | ||
* The synchronous version of `_writeSettingsFile()`. | ||
* | ||
* @see _writeSettingsFile | ||
* @private | ||
*/ | ||
_writeSettingsFileSync(obj, options) { | ||
debug('writing settings file...'); | ||
const opts = this._extendDefaultOptions(options); | ||
const pathToSettings = this.getSettingsFilePath(); | ||
const spaces = opts.prettify ? 2 : 0; | ||
if (opts.atomicSaving) { | ||
const tmpFilePath = `${pathToSettings}-tmp`; | ||
try { | ||
fs.outputJsonSync(tmpFilePath, obj, { spaces }); | ||
fs.renameSync(tmpFilePath, pathToSettings); | ||
} catch (e) { | ||
try { | ||
fs.unlinkSync(tmpFilePath); | ||
} catch (e) { | ||
// Do nothing. | ||
} | ||
return; | ||
case 'rename': { | ||
this._fsWatcher.close(); | ||
this._fsWatcher = null; | ||
break; | ||
} | ||
this._observeSettingsFile(true); | ||
} else { | ||
fs.outputJsonSync(pathToSettings, obj, { spaces }); | ||
this._observeSettingsFile(); | ||
} | ||
this._emitWriteEvents(); | ||
} | ||
/** | ||
* Emits the internal and public "create" events. | ||
* Watches the given key path for changes and calls the given handler | ||
* if the value changes. To unsubscribe from changes, call `dispose()` | ||
* on the Observer instance that is returned. | ||
* | ||
* @emits Settings#create | ||
* @param {string} keyPath | ||
* @param {Function} handler | ||
* @returns {Observer} | ||
* @public | ||
*/ | ||
_emitCreateEvents() { | ||
this.emit(Settings.InternalEvents.CREATE, this.getSettingsFilePath()); | ||
this.emit(Settings.Events.CREATE, this.getSettingsFilePath()); | ||
watch(keyPath, handler) { | ||
return new Observer(this, keyPath, handler); | ||
} | ||
/** | ||
* Emits the internal and public "write" events. | ||
* Returns a boolean indicating whether the settings object contains | ||
* the given key path. | ||
* | ||
* @emits Settings#save | ||
*/ | ||
_emitWriteEvents() { | ||
this.emit(Settings.InternalEvents.WRITE); | ||
this.emit(Settings.Events.WRITE); | ||
} | ||
/** | ||
* Called when the "create" event fires. | ||
* | ||
* @private | ||
*/ | ||
_onSettingsCreate() { | ||
debug(`settings file created at ${this.getSettingsFilePath()}`); | ||
} | ||
/** | ||
* Called when the settings file has changed. | ||
* | ||
* @param {string} eventType Either "rename" or "change". | ||
* @param {string} filename The name of the file that triggered the event. | ||
*/ | ||
_onFileChange(eventType, filename) { | ||
if (eventType === Settings.FSWatcherEventTypes.CHANGE) { | ||
debug(`detected change to settings file`); | ||
this._emitWriteEvents(); | ||
} | ||
} | ||
/** | ||
* Checks if the chosen key path exists within the settings object. | ||
* | ||
* @throws if key path is not a string. | ||
* @param {string} keyPath | ||
* @returns {Promise} | ||
* @returns {boolean} | ||
* @public | ||
*/ | ||
has(keyPath) { | ||
debug(`called has() at "${keyPath}"`); | ||
const obj = this._readSettings(); | ||
const keyPathExists = Helpers.hasKeyPath(obj, keyPath); | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
return new Promise((resolve, reject) => { | ||
this._readSettingsFile().then(obj => { | ||
const keyPathExists = helpers.hasKeyPath(obj, keyPath); | ||
resolve(keyPathExists); | ||
}, reject); | ||
}); | ||
} | ||
/** | ||
* The synchronous version of `has()`. | ||
* | ||
* @see has | ||
*/ | ||
hasSync(keyPath) { | ||
debug(`called hasSync() at "${keyPath}"`); | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
const obj = this._readSettingsFileSync(); | ||
const keyPathExists = helpers.hasKeyPath(obj, keyPath); | ||
return keyPathExists; | ||
@@ -488,340 +167,94 @@ } | ||
/** | ||
* Gets the value at the chosen key path. | ||
* Returns the value at the given key path, or sets the value at that key | ||
* path to the default value, if provided, if the key does not exist. | ||
* | ||
* @param {string} [keyPath] | ||
* @returns {Promise} | ||
* @param {string} keyPath | ||
* @param {any} [defaultValue] | ||
* @returns {any} | ||
* @public | ||
*/ | ||
get(keyPath) { | ||
debug(`called get() at "${keyPath}"`); | ||
get(keyPath, defaultValue) { | ||
const obj = this._readSettings(); | ||
const keyPathExists = Helpers.hasKeyPath(obj, keyPath); | ||
const value = Helpers.getValueAtKeyPath(obj, keyPath); | ||
return new Promise((resolve, reject) => { | ||
this._readSettingsFile().then(obj => { | ||
let value = obj; | ||
if (!keyPathExists && typeof defaultValue !== 'undefined') { | ||
Helpers.setValueAtKeyPath(obj, keyPath, defaultValue); | ||
if (typeof keyPath === 'string') { | ||
value = helpers.getValueAtKeyPath(obj, keyPath); | ||
} | ||
resolve(value); | ||
}, reject); | ||
}); | ||
} | ||
/** | ||
* The synchronous version of `get()`. | ||
* | ||
* @see get | ||
*/ | ||
getSync(keyPath) { | ||
debug(`called getSync() at "${keyPath}"`); | ||
let value = this._readSettingsFileSync(); | ||
if (typeof keyPath === 'string') { | ||
value = helpers.getValueAtKeyPath(value, keyPath); | ||
return clone(defaultValue); | ||
} else { | ||
return value; | ||
} | ||
return value; | ||
} | ||
/** | ||
* Sets the value at the chosen key path. | ||
* Returns all settings. | ||
* | ||
* @throws if key path is not a string. | ||
* @throws if options is not an object. | ||
* @param {string} keyPath | ||
* @param {any} [value] | ||
* @param {Object} [options] | ||
* @returns {Promise} | ||
* @returns {Object} | ||
* @public | ||
*/ | ||
set(keyPath, value={}, options={}) { | ||
debug(`called set() at "${keyPath}"`); | ||
getAll() { | ||
const obj = this._readSettings(); | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
return new Promise((resolve, reject) => { | ||
this._readSettingsFile().then(obj => { | ||
helpers.setValueAtKeyPath(obj, keyPath, value); | ||
this._writeSettingsFile(obj, options).then(resolve, reject); | ||
}, reject); | ||
}); | ||
return obj; | ||
} | ||
/** | ||
* The synchronous version of `set()`. | ||
* Sets the value at the given key path. | ||
* | ||
* @see set | ||
*/ | ||
setSync(keyPath, value={}, options={}) { | ||
debug(`called setSync() at "${keyPath}"`); | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
const obj = this._readSettingsFileSync(); | ||
helpers.setValueAtKeyPath(obj, keyPath, value); | ||
this._writeSettingsFileSync(obj, options); | ||
} | ||
/** | ||
* Deletes the key and value at the chosen key path. | ||
* | ||
* @throws if key path is not a string. | ||
* @throws if options is not an object. | ||
* @param {string} keyPath | ||
* @param {Object} [options] | ||
* @returns {Promise} | ||
* @param {any} value | ||
* @param {Object} [opts] | ||
* @public | ||
*/ | ||
delete(keyPath, options={}) { | ||
debug(`called delete() at "${keyPath}"`); | ||
set(keyPath, value, opts) { | ||
const obj = this._readSettings(); | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
return new Promise((resolve, reject) => { | ||
this._readSettingsFile().then(obj => { | ||
helpers.deleteValueAtKeyPath(obj, keyPath); | ||
this._writeSettingsFile(obj, options).then(resolve, reject); | ||
}, reject); | ||
}); | ||
Helpers.setValueAtKeyPath(obj, keyPath, value); | ||
this._writeSettings(obj, opts); | ||
} | ||
/** | ||
* The synchronous version of `delete()`. | ||
* Sets all settings. | ||
* | ||
* @see delete | ||
* @param {Object} | ||
* @param {Object} [opts] | ||
* @public | ||
*/ | ||
deleteSync(keyPath, options={}) { | ||
debug(`called deleteSync() at "${keyPath}"`); | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
const obj = this._readSettingsFileSync(); | ||
helpers.deleteValueAtKeyPath(obj, keyPath); | ||
this._writeSettingsFileSync(obj, options); | ||
} | ||
/** | ||
* Clears all settings and replaces the file contents with an empty object. | ||
* | ||
* @throws if options is not an object. | ||
* @param {Object} [options] | ||
* @param {boolean} [options.atomicSaving=true] | ||
* @param {boolean} [options.prettify=false] | ||
* @returns {Promise} | ||
*/ | ||
clear(options={}) { | ||
debug('called clear()'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
return this._writeSettingsFile({}, options); | ||
} | ||
/** | ||
* The synchronous version of `clear()`. | ||
* | ||
* @see clear | ||
*/ | ||
clearSync(options={}) { | ||
debug('called clearSync()'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
this._writeSettingsFileSync({}, options); | ||
} | ||
/** | ||
* Sets default settings. | ||
* | ||
* @throws if defaults is not an object. | ||
* @param {Object} [options={} | ||
* @returns {Promise} | ||
*/ | ||
defaults(defaults) { | ||
assert.strictEqual(typeof defaults, 'object', 'Defaults must be an object'); | ||
this._setDefaults(defaults); | ||
} | ||
/** | ||
* Extends the current settings with the default settings. Optionally, you | ||
* may overwrite pre-existing settings with their repsective defaults by | ||
* setting `options.overwrite` to true. Set defaults using the `defaults()` | ||
* method. | ||
* | ||
* @throws if options is not an object. | ||
* @param {Object} [options] | ||
* @returns {Promise} | ||
*/ | ||
applyDefaults(options={}) { | ||
debug('called applyDefaults()'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
return new Promise((resolve, reject) => { | ||
this._readSettingsFile().then(obj => { | ||
let newObj; | ||
if (options.overwrite === true) { | ||
newObj = deepExtend({}, obj, this._defaults); | ||
} else { | ||
newObj = deepExtend({}, this._defaults, obj); | ||
} | ||
this._writeSettingsFile(newObj, options).then(resolve, reject); | ||
}); | ||
}); | ||
} | ||
/** | ||
* The synchronous version of `applyDefaults()`. | ||
* | ||
* @see applyDefaults | ||
*/ | ||
applyDefaultsSync(options={}) { | ||
debug('called applyDefaultsSync()'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
let obj = this._readSettingsFileSync(); | ||
let newObj; | ||
if (options.overwrite === true) { | ||
newObj = deepExtend({}, obj, this._defaults); | ||
} else { | ||
newObj = deepExtend({}, this._defaults, obj); | ||
setAll(obj, opts) { | ||
if (typeof obj === 'object') { | ||
this._writeSettings(obj, opts); | ||
} | ||
this._writeSettingsFileSync(newObj, options); | ||
} | ||
/** | ||
* Resets the settings to defaults. Set defaults using the `defaults()` | ||
* method. | ||
* Deletes the key and value at the given key path. | ||
* | ||
* @throws if options is not an object. | ||
* @param {Object} [options] | ||
* @returns {Promise} | ||
*/ | ||
resetToDefaults(options={}) { | ||
debug('called resetToDefaults()'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
const defaults = this._defaults; | ||
return this._writeSettingsFile(defaults, options); | ||
} | ||
/** | ||
* The synchronous version of `resetToDefaults()`. | ||
* | ||
* @see resetToDefaults | ||
*/ | ||
resetToDefaultsSync(options={}) { | ||
debug('called resetToDefaultsSync()'); | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
const defaults = this._defaults; | ||
this._writeSettingsFileSync(defaults, options); | ||
} | ||
/** | ||
* Observes the chosen key path for changes and calls the handler if the | ||
* value changes. Returns an Observer instance which has a `dispose` method. | ||
* To unsubscribe, simply call `dispose()` on the returned key path observer. | ||
* | ||
* @throws if key path is not a string. | ||
* @throws if handler is not a function. | ||
* @param {string} keyPath | ||
* @param {Function} handler | ||
* @returns {Observer} The key path observer. | ||
* @param {Object} [opts] | ||
* @public | ||
*/ | ||
observe(keyPath, handler) { | ||
assert.strictEqual(typeof keyPath, 'string', 'Key path must be a string'); | ||
assert.strictEqual(typeof handler, 'function', 'Handler must be a function'); | ||
delete(keyPath, opts) { | ||
const obj = this._readSettings(); | ||
const keyPathExists = Helpers.hasKeyPath(obj, keyPath); | ||
const observer = new Observer(this, keyPath, handler); | ||
return observer; | ||
if (keyPathExists) { | ||
Helpers.deleteValueAtKeyPath(obj, keyPath); | ||
this._writeSettings(obj, opts); | ||
} | ||
} | ||
/** | ||
* Globally configure electron-settings options. | ||
* Deletes all settings. | ||
* | ||
* @throws if options is not an object. | ||
* @param {Object} options | ||
* @param {boolean} [options.atomicSaving=true] | ||
* @param {boolean} [options.prettify=false] | ||
* @param {Object} [options.defaults] | ||
* @public | ||
*/ | ||
configure(options) { | ||
assert.strictEqual(typeof options, 'object', 'Options must be an object'); | ||
this._configureGlobalSettings(options); | ||
deleteAll() { | ||
this._writeSettings({}); | ||
} | ||
/** | ||
* Returns the path to the settings file on the disk, | ||
* | ||
* @returns {string} | ||
*/ | ||
getSettingsFilePath() { | ||
const settingsDir = Settings.DefaultOptions.settingsDir; | ||
const settingsFileName = Settings.DefaultOptions.settingsFileName; | ||
const settingsFilePath = path.join(settingsDir, settingsFileName); | ||
return settingsFilePath; | ||
} | ||
/** | ||
* Checks if the settings file currently exists on the disk. | ||
* | ||
* @returns {boolean} | ||
*/ | ||
settingsFileExists() { | ||
const pathToSettings = this.getSettingsFilePath(); | ||
const fileExists = exists(pathToSettings); | ||
return fileExists; | ||
} | ||
/** | ||
* Why doesn't this exist? | ||
* | ||
* @alias EventListener.removeListener | ||
*/ | ||
off() { | ||
return this.removeListener.apply(this, arguments); | ||
} | ||
} | ||
/** | ||
* Default save options. | ||
* ElectronSettings event names. | ||
* | ||
* @type {Object} | ||
* @readonly | ||
*/ | ||
Settings.DefaultOptions = { | ||
atomicSaving: true, | ||
prettify: false, | ||
settingsDir: USER_DATA_PATH, | ||
settingsFileName: SETTINGS_FILE_NAME, | ||
overwrite: false | ||
}; | ||
/** | ||
* Settings event names. | ||
* | ||
* @enum {string} | ||
@@ -831,38 +264,5 @@ * @readonly | ||
Settings.Events = { | ||
CREATE: 'create', | ||
WRITE: 'write', | ||
CHANGE: 'change' | ||
}; | ||
/** | ||
* Settings internal event names. | ||
* | ||
* @enum {string} | ||
* @readonly | ||
*/ | ||
Settings.InternalEvents = { | ||
CREATE: '_create', | ||
WRITE: '_write', | ||
CHANGE: '_change' | ||
}; | ||
/** | ||
* FSWatcher event types. | ||
* | ||
* @enum {string} | ||
* @readonly | ||
*/ | ||
Settings.FSWatcherEventTypes = { | ||
CHANGE: 'change', | ||
RENAME: 'rename' | ||
}; | ||
/** | ||
* The Settings instance. | ||
* | ||
* @type {Settings} | ||
* @readonly | ||
*/ | ||
Settings.Instance = new Settings(); | ||
module.exports = Settings.Instance; | ||
module.exports = Settings; |
ISC License | ||
Copyright (c) 2016, Nathan Buchar | ||
Copyright (c) 2017, Nathan Buchar | ||
@@ -5,0 +5,0 @@ Permission to use, copy, modify, and/or distribute this software for any |
{ | ||
"name": "electron-settings", | ||
"version": "2.2.4", | ||
"version": "3.0.0", | ||
"description": "A simple persistent user settings manager for Electron.", | ||
@@ -11,2 +11,8 @@ "main": "index.js", | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/nathanbuchar/electron-settings" | ||
}, | ||
"author": "Nathan Buchar <hello@nathanbuchar.com>", | ||
"license": "ISC", | ||
"keywords": [ | ||
@@ -18,46 +24,25 @@ "electron", | ||
"settings", | ||
"manager", | ||
"config", | ||
"storage", | ||
"json", | ||
"storage", | ||
"promise", | ||
"has", | ||
"get", | ||
"getAll", | ||
"set", | ||
"setAll", | ||
"delete", | ||
"reset", | ||
"clear" | ||
"deleteAll", | ||
"watch" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/nathanbuchar/electron-settings" | ||
}, | ||
"author": "Nathan Buchar <hello@nathanbuchar.com>", | ||
"contributors": [ | ||
{ | ||
"name": "Nathan Buchar", | ||
"email": "hello@nathanbuchar.com", | ||
"web": "http://nathanbuchar,com/" | ||
}, | ||
{ | ||
"name": "Kai Eichinger", | ||
"email": "kai.eichinger@outlook.com" | ||
} | ||
], | ||
"license": "ISC", | ||
"dependencies": { | ||
"clone": "^1.0.2", | ||
"debug": "^2.2.0", | ||
"clone": "^2.1.1", | ||
"deep-equal": "^1.0.1", | ||
"deep-extend": "^0.4.1", | ||
"file-exists": "^2.0.0", | ||
"fs-extra": "^0.30.0", | ||
"key-path-helpers": "^0.4.0" | ||
"fs-extra": "^2.1.2" | ||
}, | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"electron-mocha": "^3.0.0", | ||
"electron-prebuilt": "^1.2.6", | ||
"mocha": "^3.0.2" | ||
"electron-mocha": "^3.4.0", | ||
"electron-prebuilt": "^1.4.13", | ||
"mocha": "^3.2.0" | ||
} | ||
} |
449
README.md
@@ -1,14 +0,9 @@ | ||
electron-settings | ||
================= | ||
# electron-settings | ||
**:warning: Sorry, project not currently in active development. Try [electron-json-storage](https://www.npmjs.com/package/electron-json-storage) :warning:** | ||
A simple persistent user settings manager for [Electron][external_electron]. | ||
*** | ||
Originally adapted from Atom's own configuration manager, electron-settings allows you to save your users' settings to the disk so that they can be loaded in the next time your app starts without skipping a beat. | ||
A simple persistent user settings manager for [Electron][external_electron]. Originally adapted from [Atom's own configuration manager][external_atom-config], electron-settings allows you to save user settings to the disk so that they can be loaded in the next time your app starts. | ||
Also, you can [subscribe to settings and get notified when their value changes][section_methods_watch]. So that's pretty neat. | ||
Also, you can [observe key paths][method_observe] and get notified if their value changes. So that's pretty neat. | ||
**Note:** v2 is not compatible with earlier versions of electron-settings. | ||
[![npm version](https://badge.fury.io/js/electron-settings.svg)](http://badge.fury.io/js/electron-settings) | ||
@@ -25,12 +20,10 @@ [![dependencies](https://david-dm.org/nathanbuchar/electron-settings.svg)](https://david-dm.org/nathanbuchar/electron-settings) | ||
Install | ||
--------- | ||
## Install | ||
``` | ||
$ npm install electron-settings | ||
$ npm install --save electron-settings | ||
``` | ||
Quick Start | ||
----------- | ||
## Demo | ||
@@ -43,94 +36,382 @@ ```js | ||
last: 'Kramer' | ||
}).then(() => { | ||
settings.get('name.first').then(val => { | ||
console.log(val); | ||
// => "Cosmo" | ||
}); | ||
}); | ||
settings.getSettingsFilePath(); | ||
// => /Users/You/Library/Application Support/YourApp/Settings | ||
settings.get('name.first'); | ||
// => "Cosmo" | ||
settings.has('name.middle'); | ||
// => false | ||
``` | ||
Default Settings | ||
---------------- | ||
## FAQ | ||
You can configure default settings by using [`settings.defaults()`][method_defaults]. This will set the defaults object globally. If this is the first time the settings file is being accessed, the defaults will be applied automatically. | ||
* **What is a "key path"?** | ||
```js | ||
settings.defaults({ | ||
foo: 'bar' | ||
}); | ||
With electron-settings, you are not just setting keys like you would with local storage. Instead, you are working with a JSON object, and a key path is a string that points to a specific key within that object—essentially using object dot notation in string form. | ||
settings.get('foo').then(val => { | ||
console.log(val); | ||
// => 'bar' | ||
}); | ||
``` | ||
For example, in the JSON object below the value at the key path `"foo"` is the object `{ bar: 'baz' }`, and the value at the key path `"foo.bar"` is the string `"baz"`. | ||
Additionally, you can use [`applyDefaults()`][method_apply-defaults] or [`resetToDefaults()`][method_reset-to-defaults] to fit your needs. | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
* **What data types may be stored?** | ||
You may set a key path to any value supported by JSON: an **object**, **array**, **string**, **number**, **boolean**, or **`null`**. | ||
FAQ | ||
--- | ||
Unfortunately, dates and other special object types will be type converted and lost, because JSON does not support anything other than the aforementioned data types. | ||
* **What is a "key path"?** | ||
* **Where is the settings file saved?** | ||
With electron-settings, you are not just setting keys like you would with local storage. Instead, you are working with a JSON object, and a key path is a string that points to a specific key within that object—essentially object dot notation in string form. | ||
Settings are saved in your app's [user data directory](http://electron.atom.io/docs/api/app/#appgetpathname) in a file called `Settings`. | ||
For example, in the JSON object below the value at the key path `"foo.bar"` is `"baz"`. | ||
* `~/Library/Application Support/YourApp` on MacOS. | ||
* `%APPDATA%/YourApp` on Windows. | ||
* `$XDG_CONFIG_HOME/YourApp` or `~/.config/YourApp` on Linux. | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
* **Can I use electron-settings in both the main and renderer processes?** | ||
You bet! | ||
*** | ||
## Methods | ||
* [`has()`][section_methods_has] | ||
* [`get()`][section_methods_get] | ||
* [`getAll()`][section_methods_get-all] | ||
* [`set()`][section_methods_set] | ||
* [`setAll()`][section_methods_set-all] | ||
* [`delete()`][section_methods_delete] | ||
* [`deleteAll()`][section_methods_delete-all] | ||
* [`watch()`][section_methods_watch] | ||
*** | ||
* ### has() | ||
**`settings.has(keyPath):boolean`** | ||
Returns a boolean indicating whether the settings object contains the given key path. | ||
*** | ||
**Parameters** | ||
* **`keyPath`** *String* | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
} | ||
``` | ||
``` | ||
* **Can I use electron-settings in both the main and renderer processes?** | ||
Checks if the settings contains the key path `"foo.bar"`. | ||
```js | ||
settings.has('foo.bar'); | ||
// => true | ||
``` | ||
Yes! Just be aware that if the window closes during an async operation, data may be lost. | ||
Checks if the settings contains the key path `"qux"`. | ||
```js | ||
settings.has('qux'); | ||
// => false | ||
``` | ||
* **What data types may be stored?** | ||
You may set a key path to any value supported by JSON: an object, array, string, number, boolean, or `null`. | ||
* ### get() | ||
* **Why do I have to use promises?** | ||
**`settings.get(keyPath[, defaultValue]):any`** | ||
electron-settings reads and writes to the file system asynchronously. In order to ensure data integrity, you should use promises. Alternatively, all methods have a synchronous counterpart that you may use instead. | ||
Returns the value at the given key path, or sets the value at that key path to the default value, if provided, if the key does not exist. See also: [`getAll()`][section_methods_get-all]. | ||
* **Where is the settings file saved?** | ||
*** | ||
The settings file is named `Settings` and is saved in your app's [user data directory](http://electron.atom.io/docs/api/app/#appgetpathname): | ||
**Parameters** | ||
* `~/Library/Application Support/YourApp` on MacOS. | ||
* `%APPDATA%/YourApp` on Windows. | ||
* `$XDG_CONFIG_HOME/YourApp` or `~/.config/YourApp` on Linux. | ||
* **`keyPath`** *String* | ||
* **`defaultValue`** *Any* - The value to apply if the setting does not already exist. | ||
You can use [`getSettingsFilePath()`][method_get-settings-file-path] to get the full path to the settings file. | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
*** | ||
Gets the value at `"foo"`. | ||
```js | ||
settings.get('foo'); | ||
// => { "bar": "baz" } | ||
``` | ||
Gets the value at `"foo.bar"`. | ||
```js | ||
settings.get('foo.bar'); | ||
// => "baz" | ||
``` | ||
Gets the value at `"qux"`. | ||
```js | ||
settings.get('qux'); | ||
// => undefined | ||
``` | ||
Documentation | ||
------------- | ||
* [Events][docs_events] | ||
* [Methods][docs_methods] | ||
Gets the value at `"qux"`, with a default fallback. | ||
```js | ||
settings.get('qux', 'aqpw'); | ||
// => "aqpw" | ||
``` | ||
Contributors | ||
------- | ||
* [Nathan Buchar](mailto:hello@nathanbuchar.com) (Owner) | ||
* [Kai Eichinger](mailto:kai.eichinger@outlook.com) | ||
* *You?* | ||
* ### getAll() | ||
**`settings.getAll():Object`** | ||
License | ||
------- | ||
Returns all settings. See also: [`get()`][section_methods_get]. | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
Gets all settings. | ||
```js | ||
settings.getAll(); | ||
// => { "foo": { "bar": "baz" } } | ||
``` | ||
* ### set() | ||
**`settings.set(keyPath, value[, options])`** | ||
Sets the value at the given key path. See also: [`setAll()`][section_methods_set-all]. | ||
*** | ||
**Parameters** | ||
* **`keyPath`** *String* - The path to the key whose value we wish to set. This key need not already exist. | ||
* **`value`** *Any* - The value to set the key at the chosen key path to. This must be a data type supported by JSON: an object, array, string, number, boolean, or `null`. | ||
* **`options`** *Object* (optional) | ||
* `prettify` *Boolean* (optional) - Prettify the JSON output. Defaults to `false`. | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
Changing the value at the key path `"foo.bar"` from `"baz"` to `"qux"`. | ||
```js | ||
settings.set('foo.bar', 'qux'); | ||
settings.get('foo.bar'); | ||
// => "qux" | ||
``` | ||
Setting the value at the key path `"new.key"`. | ||
```js | ||
settings.set('new', 'hotness'); | ||
settings.get('new'); | ||
// => "hotness" | ||
``` | ||
* ### setAll() | ||
**`settings.setAll(obj[, options])`** | ||
Sets all settings. See also: [`set()`][section_methods_set]. | ||
*** | ||
**Parameters** | ||
* **`obj`** *Object* - The new settings object. | ||
* **`options`** *Object* (optional) | ||
* `prettify` *Boolean* (optional) - Prettify the JSON output. Defaults to `false`. | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
Sets all settings. | ||
```js | ||
settings.setAll({ new: 'hotness' }); | ||
settings.getAll(); | ||
// => { "new": "hotness" } | ||
``` | ||
* ### delete() | ||
**`settings.delete(keyPath[, options])`** | ||
Deletes the key and value at the given key path. See also: [`deleteAll()`][section_methods_delete-all]. | ||
*** | ||
**Parameters** | ||
* **`keyPath`** *String* | ||
* **`options`** *Object* (optional) | ||
* `prettify` *Boolean* (optional) - Prettify the JSON output. Defaults to `false`. | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
Deleting `"foo.bar"`. | ||
```js | ||
settings.delete('foo.bar'); | ||
settings.get('foo'); | ||
// => {} | ||
``` | ||
* ### deleteAll() | ||
**`settings.deleteAll([options])`** | ||
Deletes all settings. See also: [`delete()`][section_methods_delete]. | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
Deletes all settings. | ||
```js | ||
settings.deleteAll(); | ||
settings.getAll(); | ||
// => {} | ||
``` | ||
* ### watch() | ||
**`settings.watch(keyPath, handler):Function`** | ||
Watches the given key path for changes and calls the given handler if the value changes. To unsubscribe from changes, call `dispose()` on the Observer instance that is returned. | ||
*** | ||
**Parameters** | ||
* **`keyPath`** *String* - The path to the key that we wish to watch for changes. | ||
* **`handler`** *Function* - The callback that will be invoked if the value at the chosen key path changes. Passes the following as arguments: | ||
* `newValue` *Any* | ||
* `oldValue` *Any* | ||
*** | ||
**Examples** | ||
Given: | ||
```json | ||
{ | ||
"foo": { | ||
"bar": "baz" | ||
} | ||
} | ||
``` | ||
Watch `"foo.bar"`. | ||
```js | ||
settings.watch('foo', (newValue, oldValue) => { | ||
console.log(newValue); | ||
// => "qux" | ||
}); | ||
settings.set('foo.bar', 'qux'); | ||
``` | ||
Dispose the key path watcher after the value has changed once. | ||
```js | ||
const observer = settings.watch('foo', newValue => { | ||
observer.dispose(); | ||
}); | ||
settings.set('foo', 'qux'); | ||
}); | ||
``` | ||
## Authors | ||
* [Nathan Buchar] (Owner) | ||
## License | ||
[ISC][license] | ||
@@ -140,5 +421,4 @@ | ||
*** | ||
<small>Last updated **Aug. 16th, 2016** by [Nathan Buchar].</small> | ||
**Having trouble?** [Get help on Gitter][external_gitter].</small> | ||
<small>**Having trouble?** [Get help on Gitter][external_gitter].</small> | ||
@@ -149,3 +429,2 @@ | ||
[license]: ./LICENSE.md | ||
@@ -156,20 +435,18 @@ | ||
[section_install]: #install | ||
[section_quick-start]: #quick-start | ||
[section_default-settings]: #default-settings | ||
[section_demo]: #demo | ||
[section_faq]: #faq | ||
[section_documentation]: #documentation | ||
[section_contributors]: #contributors | ||
[section_methods]: #methods | ||
[section_authors]: #authors | ||
[section_license]: #license | ||
[docs_events]: ./docs/events.md | ||
[docs_methods]: ./docs/methods.md | ||
[section_methods_has]: #has | ||
[section_methods_get]: #get | ||
[section_methods_get-all]: #getall | ||
[section_methods_set]: #set | ||
[section_methods_set-all]: #setall | ||
[section_methods_delete]: #delete | ||
[section_methods_delete-all]: #deleteall | ||
[section_methods_watch]: #watch | ||
[method_get-settings-file-path]: ./docs/methods.md#getsettingsfilepath | ||
[method_observe]: ./docs/methods.md#observe | ||
[method_defaults]: ./docs/methods.md#defaults | ||
[method_apply-defaults]: ./docs/methods.md#applydefaults | ||
[method_reset-to-defaults]: ./docs/methods.md#resettodefaults | ||
[external_electron]: https://electron.atom.com | ||
[external_atom-config]: https://github.com/atom/atom/blob/master/src/config.coffee | ||
[external_electron]: https://electron.atom.io | ||
[external_gitter]: https://gitter.im/nathanbuchar/electron-settings |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
3
0
448
20458
8
400
1
+ Addedclone@2.1.2(transitive)
+ Addedfs-extra@2.1.2(transitive)
- Removeddebug@^2.2.0
- Removeddeep-extend@^0.4.1
- Removedfile-exists@^2.0.0
- Removedkey-path-helpers@^0.4.0
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedclone@1.0.4(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddeep-extend@0.4.2(transitive)
- Removedfile-exists@2.0.0(transitive)
- Removedfs-extra@0.30.0(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedglob@7.2.3(transitive)
- Removedinflight@1.0.6(transitive)
- Removedinherits@2.0.4(transitive)
- Removedkey-path-helpers@0.4.0(transitive)
- Removedklaw@1.3.1(transitive)
- Removedminimatch@3.1.2(transitive)
- Removedms@2.0.0(transitive)
- Removedonce@1.4.0(transitive)
- Removedpath-is-absolute@1.0.1(transitive)
- Removedrimraf@2.7.1(transitive)
- Removedwrappy@1.0.2(transitive)
Updatedclone@^2.1.1
Updatedfs-extra@^2.1.2