mirror of https://github.com/jkjoy/sunpeiwen.git
468 lines
19 KiB
JavaScript
468 lines
19 KiB
JavaScript
"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;
|