@featurevisor/core 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/coverage/clover.xml +1663 -596
- package/coverage/coverage-final.json +24 -6
- package/coverage/lcov-report/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
- package/coverage/lcov-report/builder/index.html +38 -8
- package/coverage/lcov-report/builder/mutateVariables.ts.html +400 -0
- package/coverage/lcov-report/builder/mutator.ts.html +796 -0
- package/coverage/lcov-report/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/builder/traffic.ts.html +1 -1
- package/coverage/lcov-report/config/index.html +116 -0
- package/coverage/lcov-report/config/projectConfig.ts.html +676 -0
- package/coverage/lcov-report/datasource/adapter.ts.html +235 -0
- package/coverage/lcov-report/datasource/datasource.ts.html +862 -0
- package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +1354 -0
- package/coverage/lcov-report/datasource/index.html +161 -0
- package/coverage/lcov-report/datasource/index.ts.html +94 -0
- package/coverage/lcov-report/index.html +83 -38
- package/coverage/lcov-report/linter/attributeSchema.ts.html +175 -0
- package/coverage/lcov-report/linter/checkCircularDependency.ts.html +220 -0
- package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +268 -0
- package/coverage/lcov-report/linter/conditionSchema.ts.html +38 -38
- package/coverage/lcov-report/linter/featureSchema.ts.html +956 -485
- package/coverage/lcov-report/linter/groupSchema.ts.html +226 -0
- package/coverage/lcov-report/linter/index.html +139 -19
- package/coverage/lcov-report/linter/lintProject.ts.html +1840 -0
- package/coverage/lcov-report/linter/mutationNotation.ts.html +1309 -0
- package/coverage/lcov-report/linter/printError.ts.html +238 -0
- package/coverage/lcov-report/linter/schema.ts.html +116 -116
- package/coverage/lcov-report/linter/segmentSchema.ts.html +5 -5
- package/coverage/lcov-report/linter/testSchema.ts.html +550 -0
- package/coverage/lcov-report/list/index.html +1 -1
- package/coverage/lcov-report/list/matrix.ts.html +1 -1
- package/coverage/lcov-report/parsers/index.html +20 -5
- package/coverage/lcov-report/parsers/index.ts.html +151 -0
- package/coverage/lcov-report/parsers/json.ts.html +2 -2
- package/coverage/lcov-report/parsers/yml.ts.html +6 -6
- package/coverage/lcov-report/tester/cliFormat.ts.html +103 -0
- package/coverage/lcov-report/tester/helpers.ts.html +1 -1
- package/coverage/lcov-report/tester/index.html +20 -5
- package/coverage/lcov-report/utils/git.ts.html +481 -0
- package/coverage/lcov-report/utils/index.html +116 -0
- package/coverage/lcov.info +3253 -1214
- package/lib/builder/buildDatafile.js +8 -3
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/mutateVariables.d.ts +14 -0
- package/lib/builder/mutateVariables.js +90 -0
- package/lib/builder/mutateVariables.js.map +1 -0
- package/lib/builder/mutateVariables.spec.d.ts +1 -0
- package/lib/builder/mutateVariables.spec.js +1045 -0
- package/lib/builder/mutateVariables.spec.js.map +1 -0
- package/lib/builder/mutator.d.ts +20 -0
- package/lib/builder/mutator.js +223 -0
- package/lib/builder/mutator.js.map +1 -0
- package/lib/builder/mutator.spec.d.ts +1 -0
- package/lib/builder/mutator.spec.js +368 -0
- package/lib/builder/mutator.spec.js.map +1 -0
- package/lib/linter/featureSchema.d.ts +7 -7
- package/lib/linter/featureSchema.js +138 -62
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/featureSchema.spec.js +150 -0
- package/lib/linter/featureSchema.spec.js.map +1 -1
- package/lib/linter/lintProject.d.ts +17 -1
- package/lib/linter/lintProject.js +191 -198
- package/lib/linter/lintProject.js.map +1 -1
- package/lib/linter/lintProject.spec.d.ts +1 -0
- package/lib/linter/lintProject.spec.js +86 -0
- package/lib/linter/lintProject.spec.js.map +1 -0
- package/lib/linter/mutationNotation.d.ts +47 -0
- package/lib/linter/mutationNotation.js +381 -0
- package/lib/linter/mutationNotation.js.map +1 -0
- package/lib/linter/mutationNotation.spec.d.ts +1 -0
- package/lib/linter/mutationNotation.spec.js +549 -0
- package/lib/linter/mutationNotation.spec.js.map +1 -0
- package/lib/linter/printError.d.ts +7 -0
- package/lib/linter/printError.js +30 -15
- package/lib/linter/printError.js.map +1 -1
- package/package.json +2 -2
- package/src/builder/buildDatafile.ts +29 -3
- package/src/builder/mutateVariables.spec.ts +1134 -0
- package/src/builder/mutateVariables.ts +105 -0
- package/src/builder/mutator.spec.ts +413 -0
- package/src/builder/mutator.ts +237 -0
- package/src/linter/featureSchema.spec.ts +182 -0
- package/src/linter/featureSchema.ts +254 -97
- package/src/linter/lintProject.spec.ts +115 -0
- package/src/linter/lintProject.ts +256 -254
- package/src/linter/mutationNotation.spec.ts +642 -0
- package/src/linter/mutationNotation.ts +408 -0
- package/src/linter/printError.ts +40 -16
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
|
|
4
4
|
import type { Schema } from "@featurevisor/types";
|
|
5
|
+
import type { ZodError } from "zod";
|
|
5
6
|
|
|
6
7
|
import { getAttributeZodSchema } from "./attributeSchema";
|
|
7
8
|
import { getConditionsZodSchema } from "./conditionSchema";
|
|
@@ -13,15 +14,34 @@ import { getTestsZodSchema } from "./testSchema";
|
|
|
13
14
|
|
|
14
15
|
import { checkForCircularDependencyInRequired } from "./checkCircularDependency";
|
|
15
16
|
import { checkForFeatureExceedingGroupSlotPercentage } from "./checkPercentageExceedingSlot";
|
|
16
|
-
import { printZodError } from "./printError";
|
|
17
|
+
import { getLintIssuesFromZodError, printZodError } from "./printError";
|
|
17
18
|
import { Dependencies } from "../dependencies";
|
|
18
19
|
import { CLI_FORMAT_RED, CLI_FORMAT_BOLD_UNDERLINE } from "../tester/cliFormat";
|
|
19
20
|
import { Plugin } from "../cli";
|
|
20
21
|
|
|
22
|
+
export type LintEntityType = "attribute" | "segment" | "feature" | "group" | "schema" | "test";
|
|
23
|
+
|
|
21
24
|
export interface LintProjectOptions {
|
|
22
25
|
keyPattern?: string;
|
|
23
26
|
entityType?: string;
|
|
24
27
|
authors?: boolean;
|
|
28
|
+
json?: boolean;
|
|
29
|
+
pretty?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LintErrorItem {
|
|
33
|
+
filePath: string;
|
|
34
|
+
entityType: LintEntityType;
|
|
35
|
+
key: string;
|
|
36
|
+
message: string;
|
|
37
|
+
path: (string | number)[];
|
|
38
|
+
code?: string;
|
|
39
|
+
value?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface LintResult {
|
|
43
|
+
hasError: boolean;
|
|
44
|
+
errors: LintErrorItem[];
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
const ENTITY_NAME_REGEX = /^[a-zA-Z0-9_\-./]+$/;
|
|
@@ -40,12 +60,19 @@ async function getAuthorsOfEntity(datasource, entityType, entityKey): Promise<st
|
|
|
40
60
|
export async function lintProject(
|
|
41
61
|
deps: Dependencies,
|
|
42
62
|
options: LintProjectOptions = {},
|
|
43
|
-
): Promise<
|
|
63
|
+
): Promise<LintResult> {
|
|
44
64
|
const { projectConfig, datasource } = deps;
|
|
45
65
|
|
|
46
|
-
|
|
66
|
+
const isJsonMode = options.json === true;
|
|
67
|
+
const errors: LintErrorItem[] = [];
|
|
47
68
|
|
|
48
|
-
function
|
|
69
|
+
function log(...args: unknown[]) {
|
|
70
|
+
if (!isJsonMode) {
|
|
71
|
+
console.log(...args);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getFullPathFromKey(type: LintEntityType, key: string, relative = false) {
|
|
49
76
|
const fileName = `${key}.${datasource.getExtension()}`;
|
|
50
77
|
let fullPath = "";
|
|
51
78
|
|
|
@@ -59,10 +86,8 @@ export async function lintProject(
|
|
|
59
86
|
fullPath = path.join(projectConfig.groupsDirectoryPath, fileName);
|
|
60
87
|
} else if (type === "schema") {
|
|
61
88
|
fullPath = path.join(projectConfig.schemasDirectoryPath, fileName);
|
|
62
|
-
} else if (type === "test") {
|
|
63
|
-
fullPath = path.join(projectConfig.testsDirectoryPath, fileName);
|
|
64
89
|
} else {
|
|
65
|
-
|
|
90
|
+
fullPath = path.join(projectConfig.testsDirectoryPath, fileName);
|
|
66
91
|
}
|
|
67
92
|
|
|
68
93
|
if (relative) {
|
|
@@ -72,12 +97,118 @@ export async function lintProject(
|
|
|
72
97
|
return fullPath;
|
|
73
98
|
}
|
|
74
99
|
|
|
100
|
+
async function printEntityHeader(entityType: LintEntityType, key: string, fullPath: string) {
|
|
101
|
+
if (isJsonMode) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(CLI_FORMAT_BOLD_UNDERLINE, fullPath);
|
|
106
|
+
|
|
107
|
+
if (options.authors) {
|
|
108
|
+
const authors = await getAuthorsOfEntity(datasource, entityType, key);
|
|
109
|
+
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function recordError(error: LintErrorItem) {
|
|
114
|
+
errors.push(error);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function reportSimpleError({
|
|
118
|
+
entityType,
|
|
119
|
+
key,
|
|
120
|
+
fullPath,
|
|
121
|
+
message,
|
|
122
|
+
detail,
|
|
123
|
+
pathParts = [],
|
|
124
|
+
code,
|
|
125
|
+
value,
|
|
126
|
+
}: {
|
|
127
|
+
entityType: LintEntityType;
|
|
128
|
+
key: string;
|
|
129
|
+
fullPath: string;
|
|
130
|
+
message: string;
|
|
131
|
+
detail?: string;
|
|
132
|
+
pathParts?: (string | number)[];
|
|
133
|
+
code?: string;
|
|
134
|
+
value?: unknown;
|
|
135
|
+
}) {
|
|
136
|
+
recordError({
|
|
137
|
+
filePath: path.relative(process.cwd(), fullPath),
|
|
138
|
+
entityType,
|
|
139
|
+
key,
|
|
140
|
+
message,
|
|
141
|
+
path: pathParts,
|
|
142
|
+
code,
|
|
143
|
+
value,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!isJsonMode) {
|
|
147
|
+
await printEntityHeader(entityType, key, fullPath);
|
|
148
|
+
console.log(CLI_FORMAT_RED, ` => Error: ${message}`);
|
|
149
|
+
if (detail) {
|
|
150
|
+
console.log(CLI_FORMAT_RED, ` ${detail}`);
|
|
151
|
+
}
|
|
152
|
+
console.log("");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function reportThrownError(
|
|
157
|
+
entityType: LintEntityType,
|
|
158
|
+
key: string,
|
|
159
|
+
fullPath: string,
|
|
160
|
+
error: unknown,
|
|
161
|
+
) {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
|
|
164
|
+
recordError({
|
|
165
|
+
filePath: path.relative(process.cwd(), fullPath),
|
|
166
|
+
entityType,
|
|
167
|
+
key,
|
|
168
|
+
message,
|
|
169
|
+
path: [],
|
|
170
|
+
code: error instanceof Error ? error.name : undefined,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!isJsonMode) {
|
|
174
|
+
await printEntityHeader(entityType, key, fullPath);
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log(error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function reportZodValidationError(
|
|
181
|
+
entityType: LintEntityType,
|
|
182
|
+
key: string,
|
|
183
|
+
fullPath: string,
|
|
184
|
+
error: ZodError,
|
|
185
|
+
) {
|
|
186
|
+
const issues = getLintIssuesFromZodError(error);
|
|
187
|
+
|
|
188
|
+
issues.forEach((issue) => {
|
|
189
|
+
recordError({
|
|
190
|
+
filePath: path.relative(process.cwd(), fullPath),
|
|
191
|
+
entityType,
|
|
192
|
+
key,
|
|
193
|
+
message: issue.message,
|
|
194
|
+
path: issue.path,
|
|
195
|
+
code: issue.code,
|
|
196
|
+
value: issue.value,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!isJsonMode) {
|
|
201
|
+
await printEntityHeader(entityType, key, fullPath);
|
|
202
|
+
printZodError(error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
75
206
|
const keyPattern = options.keyPattern ? new RegExp(options.keyPattern) : null;
|
|
76
207
|
|
|
77
208
|
if (keyPattern) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
209
|
+
log("");
|
|
210
|
+
log(`Linting only keys matching pattern: ${keyPattern}`);
|
|
211
|
+
log("");
|
|
81
212
|
}
|
|
82
213
|
|
|
83
214
|
// lint attributes
|
|
@@ -90,57 +221,32 @@ export async function lintProject(
|
|
|
90
221
|
: attributes.filter((key) => keyPattern.test(key));
|
|
91
222
|
|
|
92
223
|
if (filteredKeys.length > 0) {
|
|
93
|
-
|
|
224
|
+
log(`Linting ${filteredKeys.length} attributes...\n`);
|
|
94
225
|
}
|
|
95
226
|
|
|
96
227
|
for (const key of filteredKeys) {
|
|
97
228
|
const fullPath = getFullPathFromKey("attribute", key);
|
|
98
229
|
|
|
99
230
|
if (!ATTRIBUTE_NAME_REGEX.test(key)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.log(CLI_FORMAT_RED, ` ${ATTRIBUTE_NAME_REGEX_ERROR}`);
|
|
109
|
-
console.log("");
|
|
110
|
-
hasError = true;
|
|
231
|
+
await reportSimpleError({
|
|
232
|
+
entityType: "attribute",
|
|
233
|
+
key,
|
|
234
|
+
fullPath,
|
|
235
|
+
message: `Invalid name: "${key}"`,
|
|
236
|
+
detail: ATTRIBUTE_NAME_REGEX_ERROR,
|
|
237
|
+
code: "invalid_name",
|
|
238
|
+
});
|
|
111
239
|
}
|
|
112
240
|
|
|
113
241
|
try {
|
|
114
242
|
const parsed = await datasource.readAttribute(key);
|
|
115
|
-
|
|
116
243
|
const result = attributeZodSchema.safeParse(parsed);
|
|
117
244
|
|
|
118
|
-
if (!result.success) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (options.authors) {
|
|
122
|
-
const authors = await getAuthorsOfEntity(datasource, "attribute", key);
|
|
123
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if ("error" in result) {
|
|
127
|
-
printZodError(result.error);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
hasError = true;
|
|
245
|
+
if (!result.success && "error" in result) {
|
|
246
|
+
await reportZodValidationError("attribute", key, fullPath, result.error);
|
|
131
247
|
}
|
|
132
|
-
} catch (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (options.authors) {
|
|
136
|
-
const authors = await getAuthorsOfEntity(datasource, "attribute", key);
|
|
137
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log("");
|
|
141
|
-
console.log(e);
|
|
142
|
-
|
|
143
|
-
hasError = true;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
await reportThrownError("attribute", key, fullPath, error);
|
|
144
250
|
}
|
|
145
251
|
}
|
|
146
252
|
}
|
|
@@ -159,57 +265,32 @@ export async function lintProject(
|
|
|
159
265
|
const filteredKeys = !keyPattern ? segments : segments.filter((key) => keyPattern.test(key));
|
|
160
266
|
|
|
161
267
|
if (filteredKeys.length > 0) {
|
|
162
|
-
|
|
268
|
+
log(`Linting ${filteredKeys.length} segments...\n`);
|
|
163
269
|
}
|
|
164
270
|
|
|
165
271
|
for (const key of filteredKeys) {
|
|
166
272
|
const fullPath = getFullPathFromKey("segment", key);
|
|
167
273
|
|
|
168
274
|
if (!ENTITY_NAME_REGEX.test(key)) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
console.log(CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
|
|
178
|
-
console.log("");
|
|
179
|
-
hasError = true;
|
|
275
|
+
await reportSimpleError({
|
|
276
|
+
entityType: "segment",
|
|
277
|
+
key,
|
|
278
|
+
fullPath,
|
|
279
|
+
message: `Invalid name: "${key}"`,
|
|
280
|
+
detail: ENTITY_NAME_REGEX_ERROR,
|
|
281
|
+
code: "invalid_name",
|
|
282
|
+
});
|
|
180
283
|
}
|
|
181
284
|
|
|
182
285
|
try {
|
|
183
286
|
const parsed = await datasource.readSegment(key);
|
|
184
|
-
|
|
185
287
|
const result = segmentZodSchema.safeParse(parsed);
|
|
186
288
|
|
|
187
|
-
if (!result.success) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (options.authors) {
|
|
191
|
-
const authors = await getAuthorsOfEntity(datasource, "segment", key);
|
|
192
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if ("error" in result) {
|
|
196
|
-
printZodError(result.error);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
hasError = true;
|
|
200
|
-
}
|
|
201
|
-
} catch (e) {
|
|
202
|
-
console.log(CLI_FORMAT_BOLD_UNDERLINE, fullPath);
|
|
203
|
-
|
|
204
|
-
if (options.authors) {
|
|
205
|
-
const authors = await getAuthorsOfEntity(datasource, "segment", key);
|
|
206
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
289
|
+
if (!result.success && "error" in result) {
|
|
290
|
+
await reportZodValidationError("segment", key, fullPath, result.error);
|
|
207
291
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
console.log(e);
|
|
211
|
-
|
|
212
|
-
hasError = true;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
await reportThrownError("segment", key, fullPath, error);
|
|
213
294
|
}
|
|
214
295
|
}
|
|
215
296
|
}
|
|
@@ -241,24 +322,21 @@ export async function lintProject(
|
|
|
241
322
|
const filteredKeys = !keyPattern ? features : features.filter((key) => keyPattern.test(key));
|
|
242
323
|
|
|
243
324
|
if (filteredKeys.length > 0) {
|
|
244
|
-
|
|
325
|
+
log(`Linting ${filteredKeys.length} features...\n`);
|
|
245
326
|
}
|
|
246
327
|
|
|
247
328
|
for (const key of filteredKeys) {
|
|
248
329
|
const fullPath = getFullPathFromKey("feature", key);
|
|
249
330
|
|
|
250
331
|
if (!ENTITY_NAME_REGEX.test(key)) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
console.log(CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
|
|
260
|
-
console.log("");
|
|
261
|
-
hasError = true;
|
|
332
|
+
await reportSimpleError({
|
|
333
|
+
entityType: "feature",
|
|
334
|
+
key,
|
|
335
|
+
fullPath,
|
|
336
|
+
message: `Invalid name: "${key}"`,
|
|
337
|
+
detail: ENTITY_NAME_REGEX_ERROR,
|
|
338
|
+
code: "invalid_name",
|
|
339
|
+
});
|
|
262
340
|
}
|
|
263
341
|
|
|
264
342
|
let parsed;
|
|
@@ -268,48 +346,24 @@ export async function lintProject(
|
|
|
268
346
|
|
|
269
347
|
const result = featureZodSchema.safeParse(parsed);
|
|
270
348
|
|
|
271
|
-
if (!result.success) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (options.authors) {
|
|
275
|
-
const authors = await getAuthorsOfEntity(datasource, "feature", key);
|
|
276
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if ("error" in result) {
|
|
280
|
-
printZodError(result.error);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
hasError = true;
|
|
349
|
+
if (!result.success && "error" in result) {
|
|
350
|
+
await reportZodValidationError("feature", key, fullPath, result.error);
|
|
284
351
|
}
|
|
285
|
-
} catch (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (options.authors) {
|
|
289
|
-
const authors = await getAuthorsOfEntity(datasource, "feature", key);
|
|
290
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
console.log("");
|
|
294
|
-
console.log(e);
|
|
295
|
-
|
|
296
|
-
hasError = true;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
await reportThrownError("feature", key, fullPath, error);
|
|
297
354
|
}
|
|
298
355
|
|
|
299
356
|
if (parsed && parsed.required) {
|
|
300
357
|
try {
|
|
301
358
|
await checkForCircularDependencyInRequired(datasource, key, parsed.required);
|
|
302
|
-
} catch (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
console.log(CLI_FORMAT_RED, ` => Error: ${e.message}`);
|
|
311
|
-
|
|
312
|
-
hasError = true;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
await reportSimpleError({
|
|
361
|
+
entityType: "feature",
|
|
362
|
+
key,
|
|
363
|
+
fullPath,
|
|
364
|
+
message: error instanceof Error ? error.message : String(error),
|
|
365
|
+
code: error instanceof Error ? error.name : "error",
|
|
366
|
+
});
|
|
313
367
|
}
|
|
314
368
|
}
|
|
315
369
|
}
|
|
@@ -323,24 +377,21 @@ export async function lintProject(
|
|
|
323
377
|
const filteredKeys = !keyPattern ? groups : groups.filter((key) => keyPattern.test(key));
|
|
324
378
|
|
|
325
379
|
if (filteredKeys.length > 0) {
|
|
326
|
-
|
|
380
|
+
log(`Linting ${filteredKeys.length} groups...\n`);
|
|
327
381
|
}
|
|
328
382
|
|
|
329
383
|
for (const key of filteredKeys) {
|
|
330
384
|
const fullPath = getFullPathFromKey("group", key);
|
|
331
385
|
|
|
332
386
|
if (!ENTITY_NAME_REGEX.test(key)) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
console.log(CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
|
|
342
|
-
console.log("");
|
|
343
|
-
hasError = true;
|
|
387
|
+
await reportSimpleError({
|
|
388
|
+
entityType: "group",
|
|
389
|
+
key,
|
|
390
|
+
fullPath,
|
|
391
|
+
message: `Invalid name: "${key}"`,
|
|
392
|
+
detail: ENTITY_NAME_REGEX_ERROR,
|
|
393
|
+
code: "invalid_name",
|
|
394
|
+
});
|
|
344
395
|
}
|
|
345
396
|
|
|
346
397
|
let parsed;
|
|
@@ -350,41 +401,24 @@ export async function lintProject(
|
|
|
350
401
|
|
|
351
402
|
const result = groupZodSchema.safeParse(parsed);
|
|
352
403
|
|
|
353
|
-
if (!result.success) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (options.authors) {
|
|
357
|
-
const authors = await getAuthorsOfEntity(datasource, "group", key);
|
|
358
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if ("error" in result) {
|
|
362
|
-
printZodError(result.error);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
hasError = true;
|
|
404
|
+
if (!result.success && "error" in result) {
|
|
405
|
+
await reportZodValidationError("group", key, fullPath, result.error);
|
|
366
406
|
}
|
|
367
|
-
} catch (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (options.authors) {
|
|
371
|
-
const authors = await getAuthorsOfEntity(datasource, "group", key);
|
|
372
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
console.log("");
|
|
376
|
-
console.log(e);
|
|
377
|
-
|
|
378
|
-
hasError = true;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
await reportThrownError("group", key, fullPath, error);
|
|
379
409
|
}
|
|
380
410
|
|
|
381
411
|
if (parsed) {
|
|
382
412
|
try {
|
|
383
413
|
await checkForFeatureExceedingGroupSlotPercentage(datasource, parsed, features);
|
|
384
|
-
} catch (
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
414
|
+
} catch (error) {
|
|
415
|
+
await reportSimpleError({
|
|
416
|
+
entityType: "group",
|
|
417
|
+
key,
|
|
418
|
+
fullPath,
|
|
419
|
+
message: error instanceof Error ? error.message : String(error),
|
|
420
|
+
code: error instanceof Error ? error.name : "error",
|
|
421
|
+
});
|
|
388
422
|
}
|
|
389
423
|
}
|
|
390
424
|
}
|
|
@@ -399,24 +433,21 @@ export async function lintProject(
|
|
|
399
433
|
const filteredKeys = !keyPattern ? schemas : schemas.filter((key) => keyPattern.test(key));
|
|
400
434
|
|
|
401
435
|
if (filteredKeys.length > 0) {
|
|
402
|
-
|
|
436
|
+
log(`Linting ${filteredKeys.length} schemas...\n`);
|
|
403
437
|
}
|
|
404
438
|
|
|
405
439
|
for (const key of filteredKeys) {
|
|
406
440
|
const fullPath = getFullPathFromKey("schema", key);
|
|
407
441
|
|
|
408
442
|
if (!ENTITY_NAME_REGEX.test(key)) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
console.log(CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
|
|
418
|
-
console.log("");
|
|
419
|
-
hasError = true;
|
|
443
|
+
await reportSimpleError({
|
|
444
|
+
entityType: "schema",
|
|
445
|
+
key,
|
|
446
|
+
fullPath,
|
|
447
|
+
message: `Invalid name: "${key}"`,
|
|
448
|
+
detail: ENTITY_NAME_REGEX_ERROR,
|
|
449
|
+
code: "invalid_name",
|
|
450
|
+
});
|
|
420
451
|
}
|
|
421
452
|
|
|
422
453
|
try {
|
|
@@ -424,32 +455,11 @@ export async function lintProject(
|
|
|
424
455
|
|
|
425
456
|
const result = schemaZodSchema.safeParse(parsed);
|
|
426
457
|
|
|
427
|
-
if (!result.success) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (options.authors) {
|
|
431
|
-
const authors = await getAuthorsOfEntity(datasource, "schema", key);
|
|
432
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if ("error" in result) {
|
|
436
|
-
printZodError(result.error);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
hasError = true;
|
|
458
|
+
if (!result.success && "error" in result) {
|
|
459
|
+
await reportZodValidationError("schema", key, fullPath, result.error);
|
|
440
460
|
}
|
|
441
|
-
} catch (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (options.authors) {
|
|
445
|
-
const authors = await getAuthorsOfEntity(datasource, "schema", key);
|
|
446
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
console.log("");
|
|
450
|
-
console.log(e);
|
|
451
|
-
|
|
452
|
-
hasError = true;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
await reportThrownError("schema", key, fullPath, error);
|
|
453
463
|
}
|
|
454
464
|
}
|
|
455
465
|
}
|
|
@@ -467,24 +477,21 @@ export async function lintProject(
|
|
|
467
477
|
const filteredKeys = !keyPattern ? tests : tests.filter((key) => keyPattern.test(key));
|
|
468
478
|
|
|
469
479
|
if (filteredKeys.length > 0) {
|
|
470
|
-
|
|
480
|
+
log(`Linting ${filteredKeys.length} tests...\n`);
|
|
471
481
|
}
|
|
472
482
|
|
|
473
483
|
for (const key of filteredKeys) {
|
|
474
484
|
const fullPath = getFullPathFromKey("test", key);
|
|
475
485
|
|
|
476
486
|
if (!ENTITY_NAME_REGEX.test(key)) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
console.log(CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
|
|
486
|
-
console.log("");
|
|
487
|
-
hasError = true;
|
|
487
|
+
await reportSimpleError({
|
|
488
|
+
entityType: "test",
|
|
489
|
+
key,
|
|
490
|
+
fullPath,
|
|
491
|
+
message: `Invalid name: "${key}"`,
|
|
492
|
+
detail: ENTITY_NAME_REGEX_ERROR,
|
|
493
|
+
code: "invalid_name",
|
|
494
|
+
});
|
|
488
495
|
}
|
|
489
496
|
|
|
490
497
|
try {
|
|
@@ -492,39 +499,19 @@ export async function lintProject(
|
|
|
492
499
|
|
|
493
500
|
const result = testsZodSchema.safeParse(parsed);
|
|
494
501
|
|
|
495
|
-
if (!result.success) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (options.authors) {
|
|
499
|
-
const authors = await getAuthorsOfEntity(datasource, "test", key);
|
|
500
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if ("error" in result) {
|
|
504
|
-
printZodError(result.error);
|
|
505
|
-
|
|
506
|
-
process.exit(1);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
hasError = true;
|
|
510
|
-
}
|
|
511
|
-
} catch (e) {
|
|
512
|
-
console.log(CLI_FORMAT_BOLD_UNDERLINE, fullPath);
|
|
513
|
-
|
|
514
|
-
if (options.authors) {
|
|
515
|
-
const authors = await getAuthorsOfEntity(datasource, "test", key);
|
|
516
|
-
console.log(` Authors: ${authors.join(", ")}\n`);
|
|
502
|
+
if (!result.success && "error" in result) {
|
|
503
|
+
await reportZodValidationError("test", key, fullPath, result.error);
|
|
517
504
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
console.log(e);
|
|
521
|
-
|
|
522
|
-
hasError = true;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
await reportThrownError("test", key, fullPath, error);
|
|
523
507
|
}
|
|
524
508
|
}
|
|
525
509
|
}
|
|
526
510
|
|
|
527
|
-
return
|
|
511
|
+
return {
|
|
512
|
+
hasError: errors.length > 0,
|
|
513
|
+
errors,
|
|
514
|
+
};
|
|
528
515
|
}
|
|
529
516
|
|
|
530
517
|
export const lintPlugin: Plugin = {
|
|
@@ -532,7 +519,7 @@ export const lintPlugin: Plugin = {
|
|
|
532
519
|
handler: async function (options) {
|
|
533
520
|
const { rootDirectoryPath, projectConfig, datasource, parsed } = options;
|
|
534
521
|
|
|
535
|
-
const
|
|
522
|
+
const result = await lintProject(
|
|
536
523
|
{
|
|
537
524
|
rootDirectoryPath,
|
|
538
525
|
projectConfig,
|
|
@@ -543,10 +530,17 @@ export const lintPlugin: Plugin = {
|
|
|
543
530
|
keyPattern: parsed.keyPattern,
|
|
544
531
|
entityType: parsed.entityType,
|
|
545
532
|
authors: parsed.authors,
|
|
533
|
+
json: parsed.json,
|
|
534
|
+
pretty: parsed.pretty,
|
|
546
535
|
},
|
|
547
536
|
);
|
|
548
537
|
|
|
549
|
-
if (
|
|
538
|
+
if (parsed.json) {
|
|
539
|
+
const payload = { errors: result.errors };
|
|
540
|
+
console.log(parsed.pretty ? JSON.stringify(payload, null, 2) : JSON.stringify(payload));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (result.hasError) {
|
|
550
544
|
return false;
|
|
551
545
|
}
|
|
552
546
|
},
|
|
@@ -579,5 +573,13 @@ export const lintPlugin: Plugin = {
|
|
|
579
573
|
command: 'lint --keyPattern="abc"',
|
|
580
574
|
description: `lint only entities with keys containing "abc"`,
|
|
581
575
|
},
|
|
576
|
+
{
|
|
577
|
+
command: "lint --json",
|
|
578
|
+
description: "print lint errors as JSON",
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
command: "lint --json --pretty",
|
|
582
|
+
description: "print lint errors as pretty JSON",
|
|
583
|
+
},
|
|
582
584
|
],
|
|
583
585
|
};
|