| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- "use strict";
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
- if (!privateMap.has(receiver)) {
- throw new TypeError("attempted to set private field on non-instance");
- }
- privateMap.set(receiver, value);
- return value;
- };
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
- if (!privateMap.has(receiver)) {
- throw new TypeError("attempted to get private field on non-instance");
- }
- return privateMap.get(receiver);
- };
- var _a, _b;
- var _validator, _encryptionKey, _options, _defaultValues;
- Object.defineProperty(exports, "__esModule", { value: true });
- const fs = require("fs");
- const path = require("path");
- const crypto = require("crypto");
- const assert = require("assert");
- const events_1 = require("events");
- const dotProp = require("dot-prop");
- const makeDir = require("make-dir");
- const pkgUp = require("pkg-up");
- const envPaths = require("env-paths");
- const atomically = require("atomically");
- const ajv_1 = require("ajv");
- const ajv_formats_1 = require("ajv-formats");
- const debounceFn = require("debounce-fn");
- const semver = require("semver");
- const onetime = require("onetime");
- const encryptionAlgorithm = 'aes-256-cbc';
- const createPlainObject = () => {
- return Object.create(null);
- };
- const isExist = (data) => {
- return data !== undefined && data !== null;
- };
- // Prevent caching of this module so module.parent is always accurate
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
- delete require.cache[__filename];
- const parentDir = path.dirname((_b = (_a = module.parent) === null || _a === void 0 ? void 0 : _a.filename) !== null && _b !== void 0 ? _b : '.');
- const checkValueType = (key, value) => {
- const nonJsonTypes = new Set([
- 'undefined',
- 'symbol',
- 'function'
- ]);
- const type = typeof value;
- if (nonJsonTypes.has(type)) {
- throw new TypeError(`Setting a value of type \`${type}\` for key \`${key}\` is not allowed as it's not supported by JSON`);
- }
- };
- const INTERNAL_KEY = '__internal__';
- const MIGRATION_KEY = `${INTERNAL_KEY}.migrations.version`;
- class Conf {
- constructor(partialOptions = {}) {
- var _a;
- _validator.set(this, void 0);
- _encryptionKey.set(this, void 0);
- _options.set(this, void 0);
- _defaultValues.set(this, {});
- this._deserialize = value => JSON.parse(value);
- this._serialize = value => JSON.stringify(value, null, '\t');
- const options = {
- configName: 'config',
- fileExtension: 'json',
- projectSuffix: 'nodejs',
- clearInvalidConfig: false,
- accessPropertiesByDotNotation: true,
- ...partialOptions
- };
- const getPackageData = onetime(() => {
- const packagePath = pkgUp.sync({ cwd: parentDir });
- // Can't use `require` because of Webpack being annoying:
- // https://github.com/webpack/webpack/issues/196
- const packageData = packagePath && JSON.parse(fs.readFileSync(packagePath, 'utf8'));
- return packageData !== null && packageData !== void 0 ? packageData : {};
- });
- if (!options.cwd) {
- if (!options.projectName) {
- options.projectName = getPackageData().name;
- }
- if (!options.projectName) {
- throw new Error('Project name could not be inferred. Please specify the `projectName` option.');
- }
- options.cwd = envPaths(options.projectName, { suffix: options.projectSuffix }).config;
- }
- __classPrivateFieldSet(this, _options, options);
- if (options.schema) {
- if (typeof options.schema !== 'object') {
- throw new TypeError('The `schema` option must be an object.');
- }
- const ajv = new ajv_1.default({
- allErrors: true,
- useDefaults: true
- });
- ajv_formats_1.default(ajv);
- const schema = {
- type: 'object',
- properties: options.schema
- };
- __classPrivateFieldSet(this, _validator, ajv.compile(schema));
- for (const [key, value] of Object.entries(options.schema)) {
- if (value === null || value === void 0 ? void 0 : value.default) {
- __classPrivateFieldGet(this, _defaultValues)[key] = value.default;
- }
- }
- }
- if (options.defaults) {
- __classPrivateFieldSet(this, _defaultValues, {
- ...__classPrivateFieldGet(this, _defaultValues),
- ...options.defaults
- });
- }
- if (options.serialize) {
- this._serialize = options.serialize;
- }
- if (options.deserialize) {
- this._deserialize = options.deserialize;
- }
- this.events = new events_1.EventEmitter();
- __classPrivateFieldSet(this, _encryptionKey, options.encryptionKey);
- const fileExtension = options.fileExtension ? `.${options.fileExtension}` : '';
- this.path = path.resolve(options.cwd, `${(_a = options.configName) !== null && _a !== void 0 ? _a : 'config'}${fileExtension}`);
- const fileStore = this.store;
- const store = Object.assign(createPlainObject(), options.defaults, fileStore);
- this._validate(store);
- try {
- assert.deepEqual(fileStore, store);
- }
- catch (_b) {
- this.store = store;
- }
- if (options.watch) {
- this._watch();
- }
- if (options.migrations) {
- if (!options.projectVersion) {
- options.projectVersion = getPackageData().version;
- }
- if (!options.projectVersion) {
- throw new Error('Project version could not be inferred. Please specify the `projectVersion` option.');
- }
- this._migrate(options.migrations, options.projectVersion);
- }
- }
- get(key, defaultValue) {
- if (__classPrivateFieldGet(this, _options).accessPropertiesByDotNotation) {
- return this._get(key, defaultValue);
- }
- return key in this.store ? this.store[key] : defaultValue;
- }
- set(key, value) {
- if (typeof key !== 'string' && typeof key !== 'object') {
- throw new TypeError(`Expected \`key\` to be of type \`string\` or \`object\`, got ${typeof key}`);
- }
- if (typeof key !== 'object' && value === undefined) {
- throw new TypeError('Use `delete()` to clear values');
- }
- if (this._containsReservedKey(key)) {
- throw new TypeError(`Please don't use the ${INTERNAL_KEY} key, as it's used to manage this module internal operations.`);
- }
- const { store } = this;
- const set = (key, value) => {
- checkValueType(key, value);
- if (__classPrivateFieldGet(this, _options).accessPropertiesByDotNotation) {
- dotProp.set(store, key, value);
- }
- else {
- store[key] = value;
- }
- };
- if (typeof key === 'object') {
- const object = key;
- for (const [key, value] of Object.entries(object)) {
- set(key, value);
- }
- }
- else {
- set(key, value);
- }
- this.store = store;
- }
- /**
- Check if an item exists.
- @param key - The key of the item to check.
- */
- has(key) {
- if (__classPrivateFieldGet(this, _options).accessPropertiesByDotNotation) {
- return dotProp.has(this.store, key);
- }
- return key in this.store;
- }
- /**
- Reset items to their default values, as defined by the `defaults` or `schema` option.
- @see `clear()` to reset all items.
- @param keys - The keys of the items to reset.
- */
- reset(...keys) {
- for (const key of keys) {
- if (isExist(__classPrivateFieldGet(this, _defaultValues)[key])) {
- this.set(key, __classPrivateFieldGet(this, _defaultValues)[key]);
- }
- }
- }
- /**
- Delete an item.
- @param key - The key of the item to delete.
- */
- delete(key) {
- const { store } = this;
- if (__classPrivateFieldGet(this, _options).accessPropertiesByDotNotation) {
- dotProp.delete(store, key);
- }
- else {
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
- delete store[key];
- }
- this.store = store;
- }
- /**
- Delete all items.
- This resets known items to their default values, if defined by the `defaults` or `schema` option.
- */
- clear() {
- this.store = createPlainObject();
- for (const key of Object.keys(__classPrivateFieldGet(this, _defaultValues))) {
- this.reset(key);
- }
- }
- /**
- Watches the given `key`, calling `callback` on any changes.
- @param key - The key wo watch.
- @param callback - A callback function that is called on any changes. When a `key` is first set `oldValue` will be `undefined`, and when a key is deleted `newValue` will be `undefined`.
- @returns A function, that when called, will unsubscribe.
- */
- 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}`);
- }
- return this._handleChange(() => this.get(key), callback);
- }
- /**
- Watches the whole config object, calling `callback` on any changes.
- @param callback - A callback function that is called on any changes. When a `key` is first set `oldValue` will be `undefined`, and when a key is deleted `newValue` will be `undefined`.
- @returns A function, that when called, will unsubscribe.
- */
- onDidAnyChange(callback) {
- if (typeof callback !== 'function') {
- throw new TypeError(`Expected \`callback\` to be of type \`function\`, got ${typeof callback}`);
- }
- return this._handleChange(() => this.store, callback);
- }
- get size() {
- return Object.keys(this.store).length;
- }
- get store() {
- try {
- const data = fs.readFileSync(this.path, __classPrivateFieldGet(this, _encryptionKey) ? null : 'utf8');
- const dataString = this._encryptData(data);
- const deserializedData = this._deserialize(dataString);
- this._validate(deserializedData);
- return Object.assign(createPlainObject(), deserializedData);
- }
- catch (error) {
- if (error.code === 'ENOENT') {
- this._ensureDirectory();
- return createPlainObject();
- }
- if (__classPrivateFieldGet(this, _options).clearInvalidConfig && error.name === 'SyntaxError') {
- return createPlainObject();
- }
- throw error;
- }
- }
- set store(value) {
- this._ensureDirectory();
- this._validate(value);
- this._write(value);
- this.events.emit('change');
- }
- *[(_validator = new WeakMap(), _encryptionKey = new WeakMap(), _options = new WeakMap(), _defaultValues = new WeakMap(), Symbol.iterator)]() {
- for (const [key, value] of Object.entries(this.store)) {
- yield [key, value];
- }
- }
- _encryptData(data) {
- if (!__classPrivateFieldGet(this, _encryptionKey)) {
- return data.toString();
- }
- try {
- // Check if an initialization vector has been used to encrypt the data
- if (__classPrivateFieldGet(this, _encryptionKey)) {
- try {
- if (data.slice(16, 17).toString() === ':') {
- const initializationVector = data.slice(0, 16);
- const password = crypto.pbkdf2Sync(__classPrivateFieldGet(this, _encryptionKey), initializationVector.toString(), 10000, 32, 'sha512');
- const decipher = crypto.createDecipheriv(encryptionAlgorithm, password, initializationVector);
- data = Buffer.concat([decipher.update(Buffer.from(data.slice(17))), decipher.final()]).toString('utf8');
- }
- else {
- const decipher = crypto.createDecipher(encryptionAlgorithm, __classPrivateFieldGet(this, _encryptionKey));
- data = Buffer.concat([decipher.update(Buffer.from(data)), decipher.final()]).toString('utf8');
- }
- }
- catch (_a) { }
- }
- }
- catch (_b) { }
- return data.toString();
- }
- _handleChange(getter, callback) {
- let currentValue = getter();
- const onChange = () => {
- const oldValue = currentValue;
- const newValue = getter();
- try {
- // TODO: Use `util.isDeepStrictEqual` when targeting Node.js 10
- assert.deepEqual(newValue, oldValue);
- }
- catch (_a) {
- currentValue = newValue;
- callback.call(this, newValue, oldValue);
- }
- };
- this.events.on('change', onChange);
- return () => this.events.removeListener('change', onChange);
- }
- _validate(data) {
- if (!__classPrivateFieldGet(this, _validator)) {
- return;
- }
- const valid = __classPrivateFieldGet(this, _validator).call(this, data);
- if (valid || !__classPrivateFieldGet(this, _validator).errors) {
- return;
- }
- const errors = __classPrivateFieldGet(this, _validator).errors
- .map(({ dataPath, message = '' }) => `\`${dataPath.slice(1)}\` ${message}`);
- throw new Error('Config schema violation: ' + errors.join('; '));
- }
- _ensureDirectory() {
- // TODO: Use `fs.mkdirSync` `recursive` option when targeting Node.js 12.
- // Ensure the directory exists as it could have been deleted in the meantime.
- makeDir.sync(path.dirname(this.path));
- }
- _write(value) {
- let data = this._serialize(value);
- if (__classPrivateFieldGet(this, _encryptionKey)) {
- const initializationVector = crypto.randomBytes(16);
- const password = crypto.pbkdf2Sync(__classPrivateFieldGet(this, _encryptionKey), initializationVector.toString(), 10000, 32, 'sha512');
- const cipher = crypto.createCipheriv(encryptionAlgorithm, password, initializationVector);
- data = Buffer.concat([initializationVector, Buffer.from(':'), cipher.update(Buffer.from(data)), cipher.final()]);
- }
- // Temporary workaround for Conf being packaged in a Ubuntu Snap app.
- // See https://github.com/sindresorhus/conf/pull/82
- if (process.env.SNAP) {
- fs.writeFileSync(this.path, data);
- }
- else {
- try {
- atomically.writeFileSync(this.path, data);
- }
- catch (error) {
- // Fix for https://github.com/sindresorhus/electron-store/issues/106
- // Sometimes on Windows, we will get an EXDEV error when atomic writing
- // (even though to the same directory), so we fall back to non atomic write
- if (error.code === 'EXDEV') {
- fs.writeFileSync(this.path, data);
- return;
- }
- throw error;
- }
- }
- }
- _watch() {
- this._ensureDirectory();
- if (!fs.existsSync(this.path)) {
- this._write(createPlainObject());
- }
- fs.watch(this.path, { persistent: false }, debounceFn(() => {
- // On Linux and Windows, writing to the config file emits a `rename` event, so we skip checking the event type.
- this.events.emit('change');
- }, { wait: 100 }));
- }
- _migrate(migrations, versionToMigrate) {
- let previousMigratedVersion = this._get(MIGRATION_KEY, '0.0.0');
- const newerVersions = Object.keys(migrations)
- .filter(candidateVersion => this._shouldPerformMigration(candidateVersion, previousMigratedVersion, versionToMigrate));
- let storeBackup = { ...this.store };
- for (const version of newerVersions) {
- try {
- const migration = migrations[version];
- migration(this);
- this._set(MIGRATION_KEY, version);
- previousMigratedVersion = version;
- storeBackup = { ...this.store };
- }
- catch (error) {
- this.store = storeBackup;
- throw new Error(`Something went wrong during the migration! Changes applied to the store until this failed migration will be restored. ${error}`);
- }
- }
- if (this._isVersionInRangeFormat(previousMigratedVersion) || !semver.eq(previousMigratedVersion, versionToMigrate)) {
- this._set(MIGRATION_KEY, versionToMigrate);
- }
- }
- _containsReservedKey(key) {
- if (typeof key === 'object') {
- const firsKey = Object.keys(key)[0];
- if (firsKey === INTERNAL_KEY) {
- return true;
- }
- }
- if (typeof key !== 'string') {
- return false;
- }
- if (__classPrivateFieldGet(this, _options).accessPropertiesByDotNotation) {
- if (key.startsWith(`${INTERNAL_KEY}.`)) {
- return true;
- }
- return false;
- }
- return false;
- }
- _isVersionInRangeFormat(version) {
- return semver.clean(version) === null;
- }
- _shouldPerformMigration(candidateVersion, previousMigratedVersion, versionToMigrate) {
- if (this._isVersionInRangeFormat(candidateVersion)) {
- if (previousMigratedVersion !== '0.0.0' && semver.satisfies(previousMigratedVersion, candidateVersion)) {
- return false;
- }
- return semver.satisfies(versionToMigrate, candidateVersion);
- }
- if (semver.lte(candidateVersion, previousMigratedVersion)) {
- return false;
- }
- if (semver.gt(candidateVersion, versionToMigrate)) {
- return false;
- }
- return true;
- }
- _get(key, defaultValue) {
- return dotProp.get(this.store, key, defaultValue);
- }
- _set(key, value) {
- const { store } = this;
- dotProp.set(store, key, value);
- this.store = store;
- }
- }
- exports.default = Conf;
- // For CommonJS default export support
- module.exports = Conf;
- module.exports.default = Conf;
|