UNPKG

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