UNPKG

9.71 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 var desc = Object.getOwnPropertyDescriptor(m, k);
5 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 desc = { enumerable: true, get: function() { return m[k]; } };
7 }
8 Object.defineProperty(o, k2, desc);
9}) : (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 o[k2] = m[k];
12}));
13var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 Object.defineProperty(o, "default", { enumerable: true, value: v });
15}) : function(o, v) {
16 o["default"] = v;
17});
18var __importStar = (this && this.__importStar) || function (mod) {
19 if (mod && mod.__esModule) return mod;
20 var result = {};
21 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 __setModuleDefault(result, mod);
23 return result;
24};
25var __importDefault = (this && this.__importDefault) || function (mod) {
26 return (mod && mod.__esModule) ? mod : { "default": mod };
27};
28Object.defineProperty(exports, "__esModule", { value: true });
29const code_frame_1 = require("@babel/code-frame");
30const fs_1 = __importDefault(require("fs"));
31const json5_1 = __importDefault(require("json5"));
32const path_1 = __importDefault(require("path"));
33const util_1 = require("util");
34const write_file_atomic_1 = __importDefault(require("write-file-atomic"));
35const JsonFileError_1 = __importStar(require("./JsonFileError"));
36const writeFileAtomicAsync = (0, util_1.promisify)(write_file_atomic_1.default);
37const DEFAULT_OPTIONS = {
38 badJsonDefault: undefined,
39 jsonParseErrorDefault: undefined,
40 cantReadFileDefault: undefined,
41 ensureDir: false,
42 default: undefined,
43 json5: false,
44 space: 2,
45 addNewLineAtEOF: true,
46};
47/**
48 * The JsonFile class represents the contents of json file.
49 *
50 * It's polymorphic on "JSONObject", which is a simple type representing
51 * and object with string keys and either objects or primitive types as values.
52 * @type {[type]}
53 */
54class JsonFile {
55 constructor(file, options = {}) {
56 this.file = file;
57 this.options = options;
58 }
59 read(options) {
60 return read(this.file, this._getOptions(options));
61 }
62 async readAsync(options) {
63 return readAsync(this.file, this._getOptions(options));
64 }
65 async writeAsync(object, options) {
66 return writeAsync(this.file, object, this._getOptions(options));
67 }
68 parseJsonString(json, options) {
69 return parseJsonString(json, options);
70 }
71 async getAsync(key, defaultValue, options) {
72 return getAsync(this.file, key, defaultValue, this._getOptions(options));
73 }
74 async setAsync(key, value, options) {
75 return setAsync(this.file, key, value, this._getOptions(options));
76 }
77 async mergeAsync(sources, options) {
78 return mergeAsync(this.file, sources, this._getOptions(options));
79 }
80 async deleteKeyAsync(key, options) {
81 return deleteKeyAsync(this.file, key, this._getOptions(options));
82 }
83 async deleteKeysAsync(keys, options) {
84 return deleteKeysAsync(this.file, keys, this._getOptions(options));
85 }
86 async rewriteAsync(options) {
87 return rewriteAsync(this.file, this._getOptions(options));
88 }
89 _getOptions(options) {
90 return {
91 ...this.options,
92 ...options,
93 };
94 }
95}
96exports.default = JsonFile;
97JsonFile.read = read;
98JsonFile.readAsync = readAsync;
99JsonFile.parseJsonString = parseJsonString;
100JsonFile.writeAsync = writeAsync;
101JsonFile.getAsync = getAsync;
102JsonFile.setAsync = setAsync;
103JsonFile.mergeAsync = mergeAsync;
104JsonFile.deleteKeyAsync = deleteKeyAsync;
105JsonFile.deleteKeysAsync = deleteKeysAsync;
106JsonFile.rewriteAsync = rewriteAsync;
107function read(file, options) {
108 let json;
109 try {
110 json = fs_1.default.readFileSync(file, 'utf8');
111 }
112 catch (error) {
113 assertEmptyJsonString(json, file);
114 const defaultValue = cantReadFileDefault(options);
115 if (defaultValue === undefined) {
116 throw new JsonFileError_1.default(`Can't read JSON file: ${file}`, error, error.code, file);
117 }
118 else {
119 return defaultValue;
120 }
121 }
122 return parseJsonString(json, options, file);
123}
124async function readAsync(file, options) {
125 let json;
126 try {
127 json = await fs_1.default.promises.readFile(file, 'utf8');
128 }
129 catch (error) {
130 assertEmptyJsonString(json, file);
131 const defaultValue = cantReadFileDefault(options);
132 if (defaultValue === undefined) {
133 throw new JsonFileError_1.default(`Can't read JSON file: ${file}`, error, error.code);
134 }
135 else {
136 return defaultValue;
137 }
138 }
139 return parseJsonString(json, options);
140}
141function parseJsonString(json, options, fileName) {
142 assertEmptyJsonString(json, fileName);
143 try {
144 if (_getOption(options, 'json5')) {
145 return json5_1.default.parse(json);
146 }
147 else {
148 return JSON.parse(json);
149 }
150 }
151 catch (e) {
152 const defaultValue = jsonParseErrorDefault(options);
153 if (defaultValue === undefined) {
154 const location = locationFromSyntaxError(e, json);
155 if (location) {
156 const codeFrame = (0, code_frame_1.codeFrameColumns)(json, { start: location });
157 e.codeFrame = codeFrame;
158 e.message += `\n${codeFrame}`;
159 }
160 throw new JsonFileError_1.default(`Error parsing JSON: ${json}`, e, 'EJSONPARSE', fileName);
161 }
162 else {
163 return defaultValue;
164 }
165 }
166}
167async function getAsync(file, key, defaultValue, options) {
168 const object = await readAsync(file, options);
169 if (key in object) {
170 return object[key];
171 }
172 if (defaultValue === undefined) {
173 throw new JsonFileError_1.default(`No value at key path "${key}" in JSON object from: ${file}`);
174 }
175 return defaultValue;
176}
177async function writeAsync(file, object, options) {
178 if (options === null || options === void 0 ? void 0 : options.ensureDir) {
179 await fs_1.default.promises.mkdir(path_1.default.dirname(file), { recursive: true });
180 }
181 const space = _getOption(options, 'space');
182 const json5 = _getOption(options, 'json5');
183 const addNewLineAtEOF = _getOption(options, 'addNewLineAtEOF');
184 let json;
185 try {
186 if (json5) {
187 json = json5_1.default.stringify(object, null, space);
188 }
189 else {
190 json = JSON.stringify(object, null, space);
191 }
192 }
193 catch (e) {
194 throw new JsonFileError_1.default(`Couldn't JSON.stringify object for file: ${file}`, e);
195 }
196 const data = addNewLineAtEOF ? `${json}\n` : json;
197 await writeFileAtomicAsync(file, data, {});
198 return object;
199}
200async function setAsync(file, key, value, options) {
201 // TODO: Consider implementing some kind of locking mechanism, but
202 // it's not critical for our use case, so we'll leave it out for now
203 const object = await readAsync(file, options);
204 return writeAsync(file, { ...object, [key]: value }, options);
205}
206async function mergeAsync(file, sources, options) {
207 const object = await readAsync(file, options);
208 if (Array.isArray(sources)) {
209 Object.assign(object, ...sources);
210 }
211 else {
212 Object.assign(object, sources);
213 }
214 return writeAsync(file, object, options);
215}
216async function deleteKeyAsync(file, key, options) {
217 return deleteKeysAsync(file, [key], options);
218}
219async function deleteKeysAsync(file, keys, options) {
220 const object = await readAsync(file, options);
221 let didDelete = false;
222 for (let i = 0; i < keys.length; i++) {
223 const key = keys[i];
224 if (object.hasOwnProperty(key)) {
225 delete object[key];
226 didDelete = true;
227 }
228 }
229 if (didDelete) {
230 return writeAsync(file, object, options);
231 }
232 return object;
233}
234async function rewriteAsync(file, options) {
235 const object = await readAsync(file, options);
236 return writeAsync(file, object, options);
237}
238function jsonParseErrorDefault(options = {}) {
239 if (options.jsonParseErrorDefault === undefined) {
240 return options.default;
241 }
242 else {
243 return options.jsonParseErrorDefault;
244 }
245}
246function cantReadFileDefault(options = {}) {
247 if (options.cantReadFileDefault === undefined) {
248 return options.default;
249 }
250 else {
251 return options.cantReadFileDefault;
252 }
253}
254function _getOption(options, field) {
255 if (options) {
256 if (options[field] !== undefined) {
257 return options[field];
258 }
259 }
260 return DEFAULT_OPTIONS[field];
261}
262function locationFromSyntaxError(error, sourceString) {
263 // JSON5 SyntaxError has lineNumber and columnNumber.
264 if ('lineNumber' in error && 'columnNumber' in error) {
265 return { line: error.lineNumber, column: error.columnNumber };
266 }
267 // JSON SyntaxError only includes the index in the message.
268 const match = /at position (\d+)/.exec(error.message);
269 if (match) {
270 const index = parseInt(match[1], 10);
271 const lines = sourceString.slice(0, index + 1).split('\n');
272 return { line: lines.length, column: lines[lines.length - 1].length };
273 }
274 return null;
275}
276function assertEmptyJsonString(json, file) {
277 if ((json === null || json === void 0 ? void 0 : json.trim()) === '') {
278 throw new JsonFileError_1.EmptyJsonFileError(file);
279 }
280}
281//# sourceMappingURL=JsonFile.js.map
\No newline at end of file