This repository has been archived by the owner on Jun 5, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcli.js
executable file
·153 lines (134 loc) · 4.36 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const tv4 = require('tv4');
const _ = require('lodash');
const path = require('path');
const yaml = require('js-yaml');
const assert = require('assert');
const deepExtend = require('deep-extend');
const out = require('miniwrite').log();
const style = require('ministyle').ansi();
const reporter = require('tv4-reporter').getReporter(out, style);
const TAB = ' '.repeat(4);
const DOMAIN_LIST_KEY = '$domainList';
const DOMAINS_KEY = '$domains';
const EXTENDS_KEY = '$extends';
const options = require('commander')
.option('-i, --input <filename>', 'Input file')
.option('-o, --output-dir <dirname>', 'Output directory')
.option('-s, --schema <filename>', 'JSON Schema')
.parse(process.argv);
const configs = getConfigs(options.input);
const reports = validateConfigs(configs, options.schema);
writeConfigs(configs, options.outputDir);
if (reports.length) {
reporter.reportBulk(reports);
process.exit(1);
}
/**
* Generates domain configs.
*
* @param {String} filename - Path to config.
* @returns {Object} - Domain configs.
*/
function getConfigs(filename) {
const config = inheritConfig(loadConfig, filename, resolvePath);
const domains = config[DOMAINS_KEY];
assert(_.isObjectLike(domains), '"$domains" must be an object');
// Delete utility field.
delete config[DOMAINS_KEY];
let domainList = config[DOMAIN_LIST_KEY];
if (domainList) {
assert(Array.isArray(domainList), '"$domainList" must be an array');
delete config[DOMAIN_LIST_KEY];
} else {
domainList = Object.keys(domains);
}
return domainList.reduce((configs, domain) => {
// Inherit domain configs one from another.
const domainConfig = inheritConfig((domain) => domains[domain], domain);
// Merge with default settings.
configs[domain] = deepExtend({}, config, domainConfig);
return configs;
}, []);
}
/**
* Validates domain configs.
*
* @param {Object} configs - Domain configs.
* @param {String} schemaPath - Path to JSON schema.
* @returns {Array} - Validation reports.
*/
function validateConfigs(configs, schemaPath) {
const schema = loadConfig(schemaPath);
return Object.keys(configs).reduce((reports, domain) => {
const config = configs[domain];
const result = tv4.validateMultiple(config, schema, false, true);
if (!result.valid) {
reports.push(reporter.createTest(schema, config, domain, result));
}
return reports;
}, []);
}
/**
* Writes domain configs to disk.
*
* @param {Object} configs - Domain configs.
* @param {String} outputDir - Output directory.
*/
function writeConfigs(configs, outputDir) {
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
for (const domain in configs) {
const json = JSON.stringify(configs[domain], null, TAB);
const filename = path.resolve(outputDir, `${domain}.json`);
fs.writeFileSync(filename, json, 'utf8');
}
}
/**
* Recursively inherit configs.
*
* @param {Function} getter - Config getter.
* @param {String} key - Config key.
* @param {Function} [resolver] - Key resolver.
* @returns {Object} - Resulting config.
*/
function inheritConfig(getter, key, resolver) {
let config = getter(key);
assert(config, `Config '${key}' not found`);
if (config[EXTENDS_KEY]) {
const parentKeys = [].concat(config[EXTENDS_KEY]);
const parents = parentKeys.map((parentKey) => {
if (resolver) {
parentKey = resolver(key, parentKey);
}
return inheritConfig(getter, parentKey, resolver);
});
// Merge settings from all the parents and config itself.
config = deepExtend.apply(null, [].concat({}, parents, config));
// Delete utility field.
delete config[EXTENDS_KEY];
}
return config;
}
/**
* Loads config in YAML format.
*
* @param {String} filename - Config path.
* @returns {Object} - Config data.
*/
function loadConfig(filename) {
return yaml.load(fs.readFileSync(filename, 'utf8'));
}
/**
* Resolves relative configs paths.
*
* @param {String} baseFilename - Base path.
* @param {String} filename - Relative path.
* @returns {String} - Absolute path.
*/
function resolvePath(baseFilename, filename) {
return path.resolve(path.dirname(baseFilename), filename);
}