1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", { value: true });
|
4 | const tslib_1 = require("tslib");
|
5 | const t = tslib_1.__importStar(require("io-ts"));
|
6 | const Either_1 = require("fp-ts/lib/Either");
|
7 | const types_1 = require("./types");
|
8 | const omit_1 = require("./utils/omit");
|
9 | const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
10 | const endent_1 = tslib_1.__importDefault(require("endent"));
|
11 | const ignoreTypes = ["PartialType", "IntersectionType", "ExactType"];
|
12 | const unexpectedValue = chalk_1.default.redBright.bold;
|
13 | const errorPath = chalk_1.default.underline.bold;
|
14 |
|
15 | function 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 | }
|
30 | exports.formatError = formatError;
|
31 |
|
32 | function 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 |
|
90 | function 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 |
|
101 | async 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 | }
|
117 | exports.validatePlugins = validatePlugins;
|
118 | const shouldRecurse = [
|
119 | "PartialType",
|
120 | "IntersectionType",
|
121 | "ArrayType",
|
122 | "InterfaceType",
|
123 | ];
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | function 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 |
|
155 | exports.validateIoConfiguration = (name, configDeceleration) =>
|
156 |
|
157 | async (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 | };
|
178 | exports.validateAutoRc = exports.validateIoConfiguration(".autorc", types_1.autoRc);
|
179 |
|
180 | async 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 | }
|
190 | exports.validatePluginConfiguration = validatePluginConfiguration;
|
191 |
|
\ | No newline at end of file |