UNPKG

7.06 kBJavaScriptView Raw
1"use strict";
2/* eslint-disable @typescript-eslint/no-explicit-any */
3Object.defineProperty(exports, "__esModule", { value: true });
4const tslib_1 = require("tslib");
5const t = tslib_1.__importStar(require("io-ts"));
6const Either_1 = require("fp-ts/lib/Either");
7const types_1 = require("./types");
8const omit_1 = require("./utils/omit");
9const chalk_1 = tslib_1.__importDefault(require("chalk"));
10const endent_1 = tslib_1.__importDefault(require("endent"));
11const ignoreTypes = ["PartialType", "IntersectionType", "ExactType"];
12const unexpectedValue = chalk_1.default.redBright.bold;
13const errorPath = chalk_1.default.underline.bold;
14/** Format and error as a string */
15function formatError(error) {
16 if (typeof error === "string") {
17 return error;
18 }
19 const { path, expectedType, value } = error;
20 const formattedValue = (Array.isArray(value) &&
21 endent_1.default `
22 [
23 ${value.join(",\n")}
24 ]
25 `) ||
26 (typeof value === "object" && JSON.stringify(value, null, 2)) ||
27 value;
28 return `${errorPath(`"${path}"`)}\n\nExpected ${chalk_1.default.greenBright.bold(expectedType)} but got: ${unexpectedValue(formattedValue)}\n`;
29}
30exports.formatError = formatError;
31/** Report configuration errors */
32function reporter(validation) {
33 if (validation._tag !== "Left") {
34 return false;
35 }
36 const errors = validation.left.map((error) => {
37 let parentType = "";
38 const path = error.context
39 .filter((c) => {
40 const tag = c.type._tag;
41 const include = parentType === "ArrayType" ||
42 (!ignoreTypes.includes(tag) &&
43 parentType !== "UnionType" &&
44 parentType !== "IntersectionType");
45 parentType = tag;
46 return c.key && include;
47 })
48 .map((c) => c.key)
49 .join(".");
50 return {
51 path,
52 expectedType: error.context[error.context.length - 1].type.name,
53 value: error.value,
54 };
55 });
56 const otherErrors = [];
57 const grouped = errors.reduce((acc, item) => {
58 if (typeof item === "string") {
59 otherErrors.push(item);
60 return acc;
61 }
62 if (!acc[item.path]) {
63 acc[item.path] = [];
64 }
65 acc[item.path].push(item);
66 return acc;
67 }, {});
68 const paths = Object.keys(grouped);
69 return [
70 ...otherErrors,
71 ...Object.entries(grouped)
72 .filter(([path]) => {
73 return !paths.some((p) => p.includes(path) && p !== path);
74 })
75 .map(([path, group]) => {
76 const expectedType = group
77 .map((g) => g.expectedType)
78 .map((t) => `"${t}"`)
79 .join(" or ");
80 const value = group[0].value;
81 return {
82 expectedType,
83 path,
84 value,
85 };
86 }),
87 ];
88}
89/** Convert nested object to array of flat key paths */
90function flatKeys(obj) {
91 return Object.keys(obj)
92 .map((key) => {
93 if (typeof obj[key] === "object") {
94 return flatKeys(obj[key]).map((sub) => `${key}.${sub}`);
95 }
96 return [key];
97 })
98 .reduce((acc, item) => acc.concat(item), []);
99}
100/** Ensure plugins validation is correct. */
101async function validatePlugins(validatePlugin, rc) {
102 const errors = [];
103 if (!Array.isArray(rc.plugins)) {
104 return [];
105 }
106 await Promise.all(rc.plugins.map(async (plugin) => {
107 if (!Array.isArray(plugin)) {
108 return;
109 }
110 const pluginErrors = await validatePlugin.promise(...plugin);
111 if (pluginErrors) {
112 errors.push(...pluginErrors);
113 }
114 }));
115 return errors;
116}
117exports.validatePlugins = validatePlugins;
118const shouldRecurse = [
119 "PartialType",
120 "IntersectionType",
121 "ArrayType",
122 "InterfaceType",
123];
124/**
125 * Recurse through a io-ts type and make all objects exact.
126 * This helps us check for additional properties.
127 */
128function makeExactType(configDeceleration) {
129 let strictConfigDeclaration = configDeceleration;
130 if ("props" in configDeceleration &&
131 configDeceleration._tag !== "StrictType") {
132 const props = {};
133 Object.entries(configDeceleration.props).forEach(([propName, propType]) => {
134 props[propName] = shouldRecurse.includes(propType._tag)
135 ? makeExactType(propType)
136 : propType;
137 });
138 strictConfigDeclaration = t.exact(configDeceleration._tag === "InterfaceType"
139 ? t.interface(Object.assign({}, props))
140 : t.partial(Object.assign({}, props)));
141 }
142 else if ("types" in configDeceleration) {
143 const exactInterfaces = configDeceleration.types.map((propType) => shouldRecurse.includes(propType._tag) ? makeExactType(propType) : propType);
144 strictConfigDeclaration =
145 configDeceleration._tag === "IntersectionType"
146 ? t.intersection(exactInterfaces)
147 : t.union(exactInterfaces);
148 }
149 else if ("type" in configDeceleration) {
150 strictConfigDeclaration = t.array(makeExactType(configDeceleration.type));
151 }
152 return strictConfigDeclaration;
153}
154/** Create a function to validation a configuration based on the configDeceleration */
155exports.validateIoConfiguration = (name, configDeceleration) =>
156/** A function the will validate a configuration based on the configDeceleration */
157async (rc) => {
158 const looseRc = configDeceleration.decode(rc);
159 const errors = reporter(looseRc);
160 if (errors) {
161 return errors;
162 }
163 const exactRc = makeExactType(configDeceleration).decode(rc);
164 if (!Either_1.isRight(looseRc) || !Either_1.isRight(exactRc)) {
165 return [];
166 }
167 const correctKeys = flatKeys(exactRc.right);
168 const unknownTopKeys = Object.keys(looseRc.right).filter((k) => !(k in exactRc.right));
169 const unknownDeepKeys = flatKeys(omit_1.omit(looseRc.right, unknownTopKeys)).filter((k) => !correctKeys.includes(k));
170 const unknownKeys = [...unknownTopKeys, ...unknownDeepKeys];
171 if (unknownKeys.length === 0) {
172 return [];
173 }
174 return [
175 `${errorPath(`"${name}"`)}\n\nFound unknown configuration keys: ${unexpectedValue(unknownKeys.join(", "))}\n`,
176 ];
177};
178exports.validateAutoRc = exports.validateIoConfiguration(".autorc", types_1.autoRc);
179/** Validate a plugin's configuration. */
180async function validatePluginConfiguration(name, pluginDefinition, providedOptions) {
181 const validateConfig = exports.validateIoConfiguration(name, pluginDefinition);
182 const errors = await validateConfig(providedOptions);
183 return errors.map((error) => {
184 if (typeof error === "string") {
185 return error;
186 }
187 return Object.assign(Object.assign({}, error), { path: error.path ? `${name}.${error.path}` : name });
188 });
189}
190exports.validatePluginConfiguration = validatePluginConfiguration;
191//# sourceMappingURL=validate-config.js.map
\No newline at end of file