UNPKG

3.92 kBJavaScriptView Raw
1import fs from 'fs';
2import path from 'path';
3
4const BASE_LOCATION = Symbol('base configuration file location');
5const CONFIG_FILE_NAME = Symbol('configuration file name');
6const LOCATION = Symbol('configuration file location');
7const SERIALIZERS = Symbol('configuration file serializers');
8
9function suffixPath(configLocation, configFileName) {
10 const suffix = path.sep + configFileName;
11 return configLocation.substr(-suffix.length) !== suffix
12 ? configLocation + suffix
13 : configLocation;
14}
15
16function readJsonOrReturnObject(jsonPath) {
17 try {
18 return JSON.parse(fs.readFileSync(jsonPath));
19 } catch (_error) {
20 return {};
21 }
22}
23
24/**
25 * Manages a unified configuration file for all modules that register their config.
26 */
27export default class ConfigManager {
28 /**
29 * @param {Array} configLocation The main config file location to which changes will be stored.
30 * @param {Array} baseConfigLocation A base config file to inherit from.
31 * @param {string} configFileName The name of the config file.
32 *
33 * @constructor
34 */
35 constructor(configLocation, baseConfigLocation, configFileName) {
36 this[BASE_LOCATION] = baseConfigLocation;
37 this[CONFIG_FILE_NAME] = configFileName;
38 this[LOCATION] = configLocation;
39 this[SERIALIZERS] = {};
40
41 this.read();
42 }
43
44 /**
45 * @param {string} configName A key of the configuration object that you can share with other modules, or not.
46 * @param {*} defaultValue If no configuration is found, use this.
47 * @param {*|function(config): ?*} [serialize]
48 *
49 * @return {*}
50 */
51 registerConfig(configName, defaultValue, serialize) {
52 if (!this[configName]) {
53 this[configName] = defaultValue || null;
54 }
55
56 Object.assign(this[SERIALIZERS], {
57 [configName]: serialize,
58 });
59
60 return this[configName];
61 }
62
63 /**
64 * @return {string} The location of the configuration file.
65 */
66 getLocation() {
67 return this[LOCATION];
68 }
69
70 /**
71 * Checks the path & exist status for all possible configuration file locations.
72 *
73 * @return {Array}
74 */
75 getStatus() {
76 return [this[LOCATION], this[BASE_LOCATION]]
77 .map((configLocation) =>
78 suffixPath(configLocation, this[CONFIG_FILE_NAME])
79 )
80 .map((configLocation) => ({
81 path: configLocation,
82 exists: fs.existsSync(configLocation),
83 }));
84 }
85
86 /**
87 * Read all possible configuration files and assign them to one new object.
88 */
89 read() {
90 Object.assign(
91 this,
92 this.getStatus().reduce(
93 (config, file) =>
94 file.exists
95 ? Object.assign(
96 readJsonOrReturnObject(file.path),
97 config
98 )
99 : config,
100 {}
101 )
102 );
103 }
104
105 /**
106 * Save a stringified version of the configuration object.
107 *
108 * @return {Promise} Resolves to the successful configuration location.
109 */
110 save() {
111 const configStatus = this.getStatus();
112
113 const configLocation =
114 // The fallback on the base config file is currently required for unit tests, where
115 // options.skipCreateConfigFileInHomedir is set to true.
116 (
117 configStatus.find((loc) => loc.exists) ||
118 configStatus[configStatus.length - 1]
119 ).path;
120
121 return new Promise((resolve, reject) =>
122 fs.writeFile(configLocation, this.toString(), (error) => {
123 return error ? reject(error) : resolve(configLocation);
124 })
125 );
126 }
127
128 /**
129 * Prepare a stringified version of the configuration object - one that can be deserialized too.
130 *
131 * @return {string}
132 */
133 toString() {
134 const serializedConfig = Object.keys(this[SERIALIZERS]).reduce(
135 (config, serializerName) => {
136 const serializerFn = this[SERIALIZERS][serializerName];
137 const serialized =
138 typeof serializerFn === 'function'
139 ? serializerFn(this[serializerName])
140 : this[serializerName];
141
142 return serialized !== null && serialized !== undefined
143 ? Object.assign(config, {
144 [serializerName]: serialized,
145 })
146 : config;
147 },
148 {}
149 );
150
151 return JSON.stringify(serializedConfig, null, '\t');
152 }
153}