UNPKG

6.33 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2018, Kinvey, Inc. All rights reserved.
3 *
4 * This software is licensed to you under the Kinvey terms of service located at
5 * http://www.kinvey.com/terms-of-use. By downloading, accessing and/or using this
6 * software, you hereby accept such terms of service (and any agreement referenced
7 * therein) and agree that you have read, understand and agree to be bound by such
8 * terms of service and are of legal age to agree to such terms with Kinvey.
9 *
10 * This software contains valuable confidential and proprietary information of
11 * KINVEY, INC and is subject to applicable licensing agreements.
12 * Unauthorized reproduction, transmission or distribution of this file and its
13 * contents is a violation of applicable laws.
14 */
15
16const fs = require('fs');
17const async = require('async');
18const path = require('path');
19const logger = require('./logger.js');
20const chalk = require('chalk');
21const lodashIsPlainObject = require('lodash.isplainobject');
22const lodashIsString = require('lodash.isstring');
23const { ConfigType, ConfigFiles } = require('./Constants');
24const SchemaValidator = require('./SchemaValidator');
25const { getConfigTypeError } = require('./Utils');
26
27class ConfigFileProcessor {
28 constructor(options) {
29 this.envProcessor = options.envFileProcessor;
30 this.serviceFileProcessor = options.serviceFileProcessor;
31 this.appFileProcessor = options.appFileProcessor;
32 this.orgFileProcessor = options.orgFileProcessor;
33 }
34
35 /**
36 * Processes a config file.
37 * @param {Object} options
38 * @param {String} options.file File path
39 * @param {Constants.OperationType} options.operationType
40 * @param {Constants.ConfigType} options.configType - The expected config type.
41 * @param {String} [options.name] - Name of entity to create.
42 * @param done
43 */
44 process(options, done) {
45 let actualConfigType;
46
47 async.series([
48 (next) => {
49 ConfigFileProcessor.readJSONConfigFile(options.file, (err, parsedData) => {
50 if (err) {
51 return next(err);
52 }
53
54 options.parsedData = parsedData;
55 actualConfigType = parsedData.configType;
56 next();
57 });
58 },
59 (next) => {
60 const expectedConfigType = options.configType;
61 if (expectedConfigType !== actualConfigType) {
62 const errMsg = `You have specified the wrong type of config file. Expected: ${expectedConfigType}`;
63 return setImmediate(() => next(new Error(errMsg)));
64 }
65
66 SchemaValidator.validate(actualConfigType, options.parsedData, null, next);
67 },
68 (next) => {
69 if (actualConfigType === ConfigType.ENV) {
70 this.envProcessor.process(options, next);
71 } else if (actualConfigType === ConfigType.SERVICE) {
72 this.serviceFileProcessor.process(options, next);
73 } else if (actualConfigType === ConfigType.APP) {
74 this.appFileProcessor.process(options, next);
75 } else if (actualConfigType === ConfigType.ORG) {
76 this.orgFileProcessor.process(options, next);
77 } else {
78 return setImmediate(() => next(getConfigTypeError(actualConfigType)));
79 }
80 }
81 ], (err, results) => {
82 if (err) {
83 return done(err);
84 }
85
86 done(null, results.pop());
87 });
88 }
89
90 /**
91 * Reads a JSON configuration file and returns a JavaScript object. Does all the necessary processing and resolves all referenced files.
92 * @param {string} filePath
93 */
94 static readJSONConfigFile(file, cb) {
95 ConfigFileProcessor._readJSONConfigFile(file, 'root', (err, config) => {
96 if (!err) logger.debug('Final configuration: ' + JSON.stringify(config, null, 4));
97 cb(err, config);
98 });
99 }
100
101 static _readJSONConfigFile(file, fieldPath, cb) {
102 const dirname = path.dirname(file);
103 logger.debug('Reading JSON config from file %s', chalk.cyan(file));
104 return async.waterfall([
105 next => fs.readFile(file, next),
106 (data, next) => {
107 let json = null;
108 try {
109 logger.debug('Parsing JSON config from file: %s', chalk.cyan(file));
110 json = JSON.parse(data);
111 } catch (error) {
112 logger.warn('Invalid JSON in file %s', chalk.cyan(file));
113 return next(error);
114 }
115 return next(null, json);
116 },
117 (json, next) => {
118 logger.debug('Resolving referenced files for %s', chalk.cyan(file));
119 ConfigFileProcessor._processConfigFileValue(json, dirname, fieldPath, (err, processedValue) => {
120 if (err) {
121 logger.warn('Error while resolving referenced files for %s!', chalk.cyan(file));
122 return next(err);
123 }
124
125 logger.debug('Referenced files resolved for %s', chalk.cyan(file));
126
127 return next(null, processedValue);
128 });
129 }
130 ], cb);
131 }
132
133 static _processConfigFileValue(obj, baseFolder, fieldPath, done) {
134 if (Array.isArray(obj) || lodashIsPlainObject(obj)) {
135 async.forEachOfSeries(
136 obj,
137 (value, key, callback) => {
138 if (key === 'codeFile' || key === 'sourcePath') {
139 const resolvedPath = path.resolve(baseFolder, value);
140 obj[key] = resolvedPath;
141 return setImmediate(callback);
142 }
143
144 ConfigFileProcessor._processConfigFileValue(value, baseFolder, `${fieldPath}.${key}`, (err, processedValue) => {
145 obj[key] = processedValue;
146 callback(err, processedValue);
147 });
148 },
149 (err) => {
150 done(err, obj);
151 }
152 );
153 } else if (lodashIsString(obj)) {
154 if (obj.indexOf(ConfigFiles.FILE_REFERENCE_PREFIX) === 0) {
155 let filePath = obj.substring(ConfigFiles.FILE_REFERENCE_PREFIX.length);
156 logger.debug('Found referenced file in "%s": %s', fieldPath, chalk.cyan(filePath));
157 filePath = path.resolve(baseFolder, filePath);
158 logger.debug('Resolving config file: %s', chalk.cyan(filePath));
159 this._readJSONConfigFile(filePath, fieldPath, done);
160 } else {
161 return done(null, obj);
162 }
163 } else {
164 return done(null, obj);
165 }
166 }
167}
168
169module.exports = ConfigFileProcessor;