"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const util_1 = require("util"); const get_1 = __importDefault(require("lodash/get")); const has_1 = __importDefault(require("lodash/has")); const set_1 = __importDefault(require("lodash/set")); const json5_1 = __importDefault(require("json5")); const write_file_atomic_1 = __importDefault(require("write-file-atomic")); const code_frame_1 = require("@babel/code-frame"); const JsonFileError_1 = __importDefault(require("./JsonFileError")); const readFileAsync = util_1.promisify(fs_1.default.readFile); const writeFileAtomicAsync = util_1.promisify(write_file_atomic_1.default); const DEFAULT_OPTIONS = { badJsonDefault: undefined, jsonParseErrorDefault: undefined, cantReadFileDefault: undefined, default: undefined, json5: false, space: 2, addNewLineAtEOF: true, }; /** * The JsonFile class represents the contents of json file. * * It's polymorphic on "JSONObject", which is a simple type representing * and object with string keys and either objects or primitive types as values. * @type {[type]} */ class JsonFile { constructor(file, options = {}) { this.file = file; this.options = options; } readAsync(options) { return __awaiter(this, void 0, void 0, function* () { return readAsync(this.file, this._getOptions(options)); }); } writeAsync(object, options) { return __awaiter(this, void 0, void 0, function* () { return writeAsync(this.file, object, this._getOptions(options)); }); } getAsync(key, defaultValue, options) { return __awaiter(this, void 0, void 0, function* () { return getAsync(this.file, key, defaultValue, this._getOptions(options)); }); } setAsync(key, value, options) { return __awaiter(this, void 0, void 0, function* () { return setAsync(this.file, key, value, this._getOptions(options)); }); } mergeAsync(sources, options) { return __awaiter(this, void 0, void 0, function* () { return mergeAsync(this.file, sources, this._getOptions(options)); }); } deleteKeyAsync(key, options) { return __awaiter(this, void 0, void 0, function* () { return deleteKeyAsync(this.file, key, this._getOptions(options)); }); } deleteKeysAsync(keys, options) { return __awaiter(this, void 0, void 0, function* () { return deleteKeysAsync(this.file, keys, this._getOptions(options)); }); } rewriteAsync(options) { return __awaiter(this, void 0, void 0, function* () { return rewriteAsync(this.file, this._getOptions(options)); }); } _getOptions(options) { return Object.assign({}, this.options, options); } } JsonFile.readAsync = readAsync; JsonFile.writeAsync = writeAsync; JsonFile.getAsync = getAsync; JsonFile.setAsync = setAsync; JsonFile.mergeAsync = mergeAsync; JsonFile.deleteKeyAsync = deleteKeyAsync; JsonFile.deleteKeysAsync = deleteKeysAsync; JsonFile.rewriteAsync = rewriteAsync; exports.default = JsonFile; function readAsync(file, options) { return __awaiter(this, void 0, void 0, function* () { let json; try { json = yield readFileAsync(file, 'utf8'); } catch (error) { let defaultValue = cantReadFileDefault(options); if (defaultValue === undefined) { throw new JsonFileError_1.default(`Can't read JSON file: ${file}`, error, error.code); } else { return defaultValue; } } try { if (_getOption(options, 'json5')) { return json5_1.default.parse(json); } else { return JSON.parse(json); } } catch (e) { let defaultValue = jsonParseErrorDefault(options); if (defaultValue === undefined) { let location = locationFromSyntaxError(e, json); if (location) { let codeFrame = code_frame_1.codeFrameColumns(json, { start: location }); e.codeFrame = codeFrame; e.message += `\n${codeFrame}`; } throw new JsonFileError_1.default(`Error parsing JSON file: ${file}`, e, 'EJSONPARSE'); } else { return defaultValue; } } }); } function getAsync(file, key, defaultValue, options) { return __awaiter(this, void 0, void 0, function* () { const object = yield readAsync(file, options); if (defaultValue === undefined && !has_1.default(object, key)) { throw new JsonFileError_1.default(`No value at key path "${key}" in JSON object from: ${file}`); } return get_1.default(object, key, defaultValue); }); } function writeAsync(file, object, options) { return __awaiter(this, void 0, void 0, function* () { const space = _getOption(options, 'space'); const json5 = _getOption(options, 'json5'); const addNewLineAtEOF = _getOption(options, 'addNewLineAtEOF'); let json; try { if (json5) { json = json5_1.default.stringify(object, null, space); } else { json = JSON.stringify(object, null, space); } } catch (e) { throw new JsonFileError_1.default(`Couldn't JSON.stringify object for file: ${file}`, e); } const data = addNewLineAtEOF ? `${json}\n` : json; yield writeFileAtomicAsync(file, data, {}); return object; }); } function setAsync(file, key, value, options) { return __awaiter(this, void 0, void 0, function* () { // TODO: Consider implementing some kind of locking mechanism, but // it's not critical for our use case, so we'll leave it out for now let object = yield readAsync(file, options); object = set_1.default(object, key, value); return writeAsync(file, object, options); }); } function mergeAsync(file, sources, options) { return __awaiter(this, void 0, void 0, function* () { const object = yield readAsync(file, options); if (Array.isArray(sources)) { Object.assign(object, ...sources); } else { Object.assign(object, sources); } return writeAsync(file, object, options); }); } function deleteKeyAsync(file, key, options) { return __awaiter(this, void 0, void 0, function* () { return deleteKeysAsync(file, [key], options); }); } function deleteKeysAsync(file, keys, options) { return __awaiter(this, void 0, void 0, function* () { const object = yield readAsync(file, options); let didDelete = false; for (let i = 0; i < keys.length; i++) { let key = keys[i]; if (object.hasOwnProperty(key)) { delete object[key]; didDelete = true; } } if (didDelete) { return writeAsync(file, object, options); } return object; }); } function rewriteAsync(file, options) { return __awaiter(this, void 0, void 0, function* () { const object = yield readAsync(file, options); return writeAsync(file, object, options); }); } function jsonParseErrorDefault(options = {}) { if (options.jsonParseErrorDefault === undefined) { return options.default; } else { return options.jsonParseErrorDefault; } } function cantReadFileDefault(options = {}) { if (options.cantReadFileDefault === undefined) { return options.default; } else { return options.cantReadFileDefault; } } function _getOption(options, field) { if (options) { if (options[field] !== undefined) { return options[field]; } } return DEFAULT_OPTIONS[field]; } function locationFromSyntaxError(error, sourceString) { // JSON5 SyntaxError has lineNumber and columnNumber. if ('lineNumber' in error && 'columnNumber' in error) { return { line: error.lineNumber, column: error.columnNumber }; } // JSON SyntaxError only includes the index in the message. let match = /at position (\d+)/.exec(error.message); if (match) { let index = parseInt(match[1], 10); let lines = sourceString.slice(0, index + 1).split('\n'); return { line: lines.length, column: lines[lines.length - 1].length }; } return null; } //# sourceMappingURL=JsonFile.js.map