@jskit-ai/jskit-cli 0.2.53 → 0.2.54
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/package.json +4 -4
- package/src/server/commandHandlers/health.js +409 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.54",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.53",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.45",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.44"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": "20.x"
|
|
@@ -23,16 +23,11 @@ function createHealthCommands(ctx = {}) {
|
|
|
23
23
|
path
|
|
24
24
|
} = ctx;
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
"src/main.js",
|
|
28
|
-
"src/main.mjs",
|
|
29
|
-
"src/main.ts"
|
|
30
|
-
]);
|
|
31
|
-
const MDI_SVG_SCAN_ROOTS = Object.freeze([
|
|
26
|
+
const APP_SOURCE_SCAN_ROOTS = Object.freeze([
|
|
32
27
|
"src",
|
|
33
28
|
"packages"
|
|
34
29
|
]);
|
|
35
|
-
const
|
|
30
|
+
const APP_SOURCE_IGNORED_DIRECTORY_NAMES = new Set([
|
|
36
31
|
".git",
|
|
37
32
|
".jskit",
|
|
38
33
|
".build",
|
|
@@ -45,15 +40,34 @@ function createHealthCommands(ctx = {}) {
|
|
|
45
40
|
"tests",
|
|
46
41
|
"__tests__"
|
|
47
42
|
]);
|
|
48
|
-
const
|
|
43
|
+
const APP_SOURCE_IGNORED_FILE_PATTERNS = Object.freeze([
|
|
49
44
|
/\.spec\./i,
|
|
50
45
|
/\.test\./i,
|
|
51
46
|
/\.vitest\./i
|
|
52
47
|
]);
|
|
48
|
+
const APP_SOURCE_CODE_EXTENSIONS = new Set([
|
|
49
|
+
".cjs",
|
|
50
|
+
".js",
|
|
51
|
+
".jsx",
|
|
52
|
+
".mjs",
|
|
53
|
+
".ts",
|
|
54
|
+
".tsx",
|
|
55
|
+
".vue"
|
|
56
|
+
]);
|
|
57
|
+
const VUE_SOURCE_EXTENSIONS = new Set([".vue"]);
|
|
58
|
+
const MDI_SVG_MAIN_ENTRY_CANDIDATES = Object.freeze([
|
|
59
|
+
"src/main.js",
|
|
60
|
+
"src/main.mjs",
|
|
61
|
+
"src/main.ts"
|
|
62
|
+
]);
|
|
53
63
|
const DIRECT_MDI_LITERAL_ICON_PATTERN =
|
|
54
64
|
/<(v-[a-z0-9-]+)[^>]*?\b(icon|prepend-icon|append-icon)\s*=\s*(['"])(mdi-[^'"]+)\3/gi;
|
|
55
65
|
const DIRECT_MDI_BOUND_LITERAL_ICON_PATTERN =
|
|
56
66
|
/<(v-[a-z0-9-]+)[^>]*?(?::|v-bind:)(icon|prepend-icon|append-icon)\s*=\s*(['"])(['"])(mdi-[^'"]+)\4\3/gi;
|
|
67
|
+
const FILTER_RUNTIME_CALLEES = Object.freeze([
|
|
68
|
+
"createCrudListFilters",
|
|
69
|
+
"useCrudListFilters"
|
|
70
|
+
]);
|
|
57
71
|
|
|
58
72
|
function collectDescriptorContainerTokens({ packageId, side, values, issues }) {
|
|
59
73
|
const declaredTokens = new Set();
|
|
@@ -152,34 +166,33 @@ function createHealthCommands(ctx = {}) {
|
|
|
152
166
|
}
|
|
153
167
|
}
|
|
154
168
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const absolutePath = path.join(appRoot, relativePath);
|
|
158
|
-
if (!(await fileExists(absolutePath))) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const fileContent = await readFile(absolutePath, "utf8");
|
|
162
|
-
if (fileContent.includes("vuetify/iconsets/mdi-svg")) {
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function shouldSkipMdiSvgDoctorDirectory(directoryName = "") {
|
|
171
|
-
return MDI_SVG_IGNORED_DIRECTORY_NAMES.has(String(directoryName || "").trim());
|
|
169
|
+
function shouldSkipAppSourceDirectory(directoryName = "") {
|
|
170
|
+
return APP_SOURCE_IGNORED_DIRECTORY_NAMES.has(String(directoryName || "").trim());
|
|
172
171
|
}
|
|
173
172
|
|
|
174
|
-
function
|
|
173
|
+
function shouldSkipAppSourceFile(
|
|
174
|
+
fileName = "",
|
|
175
|
+
{
|
|
176
|
+
extensions = APP_SOURCE_CODE_EXTENSIONS,
|
|
177
|
+
ignoredFilePatterns = APP_SOURCE_IGNORED_FILE_PATTERNS
|
|
178
|
+
} = {}
|
|
179
|
+
) {
|
|
175
180
|
const normalizedFileName = String(fileName || "").trim();
|
|
176
|
-
|
|
181
|
+
const extension = path.extname(normalizedFileName).toLowerCase();
|
|
182
|
+
if (!extensions.has(extension)) {
|
|
177
183
|
return true;
|
|
178
184
|
}
|
|
179
|
-
return
|
|
185
|
+
return ignoredFilePatterns.some((pattern) => pattern.test(normalizedFileName));
|
|
180
186
|
}
|
|
181
187
|
|
|
182
|
-
async function
|
|
188
|
+
async function collectAppSourceFiles(
|
|
189
|
+
rootDirectory,
|
|
190
|
+
{
|
|
191
|
+
extensions = APP_SOURCE_CODE_EXTENSIONS,
|
|
192
|
+
ignoredFilePatterns = APP_SOURCE_IGNORED_FILE_PATTERNS
|
|
193
|
+
} = {},
|
|
194
|
+
collected = []
|
|
195
|
+
) {
|
|
183
196
|
if (!(await fileExists(rootDirectory))) {
|
|
184
197
|
return collected;
|
|
185
198
|
}
|
|
@@ -190,13 +203,26 @@ function createHealthCommands(ctx = {}) {
|
|
|
190
203
|
for (const entry of entries) {
|
|
191
204
|
const entryPath = path.join(rootDirectory, entry.name);
|
|
192
205
|
if (entry.isDirectory()) {
|
|
193
|
-
if (
|
|
206
|
+
if (shouldSkipAppSourceDirectory(entry.name)) {
|
|
194
207
|
continue;
|
|
195
208
|
}
|
|
196
|
-
await
|
|
209
|
+
await collectAppSourceFiles(
|
|
210
|
+
entryPath,
|
|
211
|
+
{
|
|
212
|
+
extensions,
|
|
213
|
+
ignoredFilePatterns
|
|
214
|
+
},
|
|
215
|
+
collected
|
|
216
|
+
);
|
|
197
217
|
continue;
|
|
198
218
|
}
|
|
199
|
-
if (
|
|
219
|
+
if (
|
|
220
|
+
entry.isFile() &&
|
|
221
|
+
!shouldSkipAppSourceFile(entry.name, {
|
|
222
|
+
extensions,
|
|
223
|
+
ignoredFilePatterns
|
|
224
|
+
})
|
|
225
|
+
) {
|
|
200
226
|
collected.push(entryPath);
|
|
201
227
|
}
|
|
202
228
|
}
|
|
@@ -204,6 +230,21 @@ function createHealthCommands(ctx = {}) {
|
|
|
204
230
|
return collected;
|
|
205
231
|
}
|
|
206
232
|
|
|
233
|
+
async function appUsesVuetifyMdiSvg(appRoot) {
|
|
234
|
+
for (const relativePath of MDI_SVG_MAIN_ENTRY_CANDIDATES) {
|
|
235
|
+
const absolutePath = path.join(appRoot, relativePath);
|
|
236
|
+
if (!(await fileExists(absolutePath))) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const fileContent = await readFile(absolutePath, "utf8");
|
|
240
|
+
if (fileContent.includes("vuetify/iconsets/mdi-svg")) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
207
248
|
function resolveLineNumberFromIndex(sourceText = "", index = 0) {
|
|
208
249
|
return String(sourceText || "").slice(0, Math.max(0, index)).split("\n").length;
|
|
209
250
|
}
|
|
@@ -228,14 +269,312 @@ function createHealthCommands(ctx = {}) {
|
|
|
228
269
|
}
|
|
229
270
|
}
|
|
230
271
|
|
|
272
|
+
function isEscapedCharacter(sourceText = "", index = 0) {
|
|
273
|
+
let backslashCount = 0;
|
|
274
|
+
for (let position = index - 1; position >= 0 && sourceText[position] === "\\"; position -= 1) {
|
|
275
|
+
backslashCount += 1;
|
|
276
|
+
}
|
|
277
|
+
return backslashCount % 2 === 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function findClosingParenIndex(sourceText = "", openParenIndex = -1) {
|
|
281
|
+
if (openParenIndex < 0 || sourceText[openParenIndex] !== "(") {
|
|
282
|
+
return -1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let parenDepth = 0;
|
|
286
|
+
let quote = "";
|
|
287
|
+
let inLineComment = false;
|
|
288
|
+
let inBlockComment = false;
|
|
289
|
+
|
|
290
|
+
for (let index = openParenIndex; index < sourceText.length; index += 1) {
|
|
291
|
+
const character = sourceText[index];
|
|
292
|
+
const nextCharacter = sourceText[index + 1] || "";
|
|
293
|
+
|
|
294
|
+
if (inLineComment) {
|
|
295
|
+
if (character === "\n") {
|
|
296
|
+
inLineComment = false;
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (inBlockComment) {
|
|
302
|
+
if (character === "*" && nextCharacter === "/") {
|
|
303
|
+
inBlockComment = false;
|
|
304
|
+
index += 1;
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (quote) {
|
|
310
|
+
if (character === quote && !isEscapedCharacter(sourceText, index)) {
|
|
311
|
+
quote = "";
|
|
312
|
+
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (character === "/" && nextCharacter === "/") {
|
|
317
|
+
inLineComment = true;
|
|
318
|
+
index += 1;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (character === "/" && nextCharacter === "*") {
|
|
323
|
+
inBlockComment = true;
|
|
324
|
+
index += 1;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (character === "'" || character === "\"" || character === "`") {
|
|
329
|
+
quote = character;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (character === "(") {
|
|
334
|
+
parenDepth += 1;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (character === ")") {
|
|
339
|
+
parenDepth -= 1;
|
|
340
|
+
if (parenDepth === 0) {
|
|
341
|
+
return index;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return -1;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function extractFirstArgumentText(argsText = "") {
|
|
350
|
+
let parenDepth = 0;
|
|
351
|
+
let braceDepth = 0;
|
|
352
|
+
let bracketDepth = 0;
|
|
353
|
+
let quote = "";
|
|
354
|
+
let inLineComment = false;
|
|
355
|
+
let inBlockComment = false;
|
|
356
|
+
|
|
357
|
+
for (let index = 0; index < argsText.length; index += 1) {
|
|
358
|
+
const character = argsText[index];
|
|
359
|
+
const nextCharacter = argsText[index + 1] || "";
|
|
360
|
+
|
|
361
|
+
if (inLineComment) {
|
|
362
|
+
if (character === "\n") {
|
|
363
|
+
inLineComment = false;
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (inBlockComment) {
|
|
369
|
+
if (character === "*" && nextCharacter === "/") {
|
|
370
|
+
inBlockComment = false;
|
|
371
|
+
index += 1;
|
|
372
|
+
}
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (quote) {
|
|
377
|
+
if (character === quote && !isEscapedCharacter(argsText, index)) {
|
|
378
|
+
quote = "";
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (character === "/" && nextCharacter === "/") {
|
|
384
|
+
inLineComment = true;
|
|
385
|
+
index += 1;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (character === "/" && nextCharacter === "*") {
|
|
390
|
+
inBlockComment = true;
|
|
391
|
+
index += 1;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (character === "'" || character === "\"" || character === "`") {
|
|
396
|
+
quote = character;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (character === "(") {
|
|
401
|
+
parenDepth += 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (character === ")") {
|
|
405
|
+
parenDepth -= 1;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (character === "{") {
|
|
409
|
+
braceDepth += 1;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (character === "}") {
|
|
413
|
+
braceDepth -= 1;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (character === "[") {
|
|
417
|
+
bracketDepth += 1;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (character === "]") {
|
|
421
|
+
bracketDepth -= 1;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (character === "," && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) {
|
|
426
|
+
return argsText.slice(0, index);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return argsText;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function collectStaticImportBindings(sourceText = "") {
|
|
434
|
+
const bindings = new Map();
|
|
435
|
+
const importPattern = /^\s*import\s+([\s\S]*?)\s+from\s+["']([^"']+)["'];?/gmu;
|
|
436
|
+
|
|
437
|
+
for (const match of sourceText.matchAll(importPattern)) {
|
|
438
|
+
const specifierText = String(match[1] || "").trim();
|
|
439
|
+
const sourcePath = String(match[2] || "").trim();
|
|
440
|
+
if (!specifierText || !sourcePath) {
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const namedMatch = specifierText.match(/\{([\s\S]*)\}/u);
|
|
445
|
+
if (namedMatch) {
|
|
446
|
+
const namedContent = String(namedMatch[1] || "");
|
|
447
|
+
for (const rawSpecifier of namedContent.split(",")) {
|
|
448
|
+
const specifier = String(rawSpecifier || "").trim();
|
|
449
|
+
if (!specifier) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const aliasMatch = specifier.match(/^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/u);
|
|
453
|
+
const localName = aliasMatch ? aliasMatch[2] : specifier;
|
|
454
|
+
if (/^[A-Za-z_$][\w$]*$/u.test(localName)) {
|
|
455
|
+
bindings.set(localName, sourcePath);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const leadingSpecifier = namedMatch
|
|
461
|
+
? specifierText.slice(0, namedMatch.index).replace(/,$/u, "").trim()
|
|
462
|
+
: specifierText;
|
|
463
|
+
if (!leadingSpecifier) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const namespaceMatch = leadingSpecifier.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/u);
|
|
468
|
+
if (namespaceMatch) {
|
|
469
|
+
bindings.set(namespaceMatch[1], sourcePath);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (/^[A-Za-z_$][\w$]*$/u.test(leadingSpecifier)) {
|
|
474
|
+
bindings.set(leadingSpecifier, sourcePath);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return bindings;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function isSharedListFiltersImportSource(sourcePath = "") {
|
|
482
|
+
return /(^|\/)shared\/[^/'"]*ListFilters(?:\.[A-Za-z0-9]+)?$/u.test(String(sourcePath || "").trim());
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function findCallSites(sourceText = "", calleeName = "") {
|
|
486
|
+
const normalizedCalleeName = String(calleeName || "").trim();
|
|
487
|
+
if (!normalizedCalleeName) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const callPattern = new RegExp(`\\b${normalizedCalleeName}\\s*\\(`, "gu");
|
|
492
|
+
const calls = [];
|
|
493
|
+
|
|
494
|
+
for (const match of sourceText.matchAll(callPattern)) {
|
|
495
|
+
const matchedText = String(match[0] || "");
|
|
496
|
+
const openParenIndex = (match.index || 0) + matchedText.lastIndexOf("(");
|
|
497
|
+
const closeParenIndex = findClosingParenIndex(sourceText, openParenIndex);
|
|
498
|
+
if (closeParenIndex < 0) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
calls.push({
|
|
503
|
+
calleeName: normalizedCalleeName,
|
|
504
|
+
index: match.index || 0,
|
|
505
|
+
openParenIndex,
|
|
506
|
+
closeParenIndex,
|
|
507
|
+
argsText: sourceText.slice(openParenIndex + 1, closeParenIndex)
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return calls;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function collectFilterDefinitionOwnershipIssues({
|
|
515
|
+
sourceText = "",
|
|
516
|
+
relativePath = "",
|
|
517
|
+
issues = []
|
|
518
|
+
}) {
|
|
519
|
+
const importBindings = collectStaticImportBindings(sourceText);
|
|
520
|
+
|
|
521
|
+
for (const calleeName of FILTER_RUNTIME_CALLEES) {
|
|
522
|
+
for (const callSite of findCallSites(sourceText, calleeName)) {
|
|
523
|
+
const lineNumber = resolveLineNumberFromIndex(sourceText, callSite.index);
|
|
524
|
+
const firstArgument = extractFirstArgumentText(callSite.argsText).trim();
|
|
525
|
+
|
|
526
|
+
if (!firstArgument || firstArgument.startsWith("{")) {
|
|
527
|
+
issues.push(
|
|
528
|
+
`${relativePath}:${lineNumber}: [filters:shared-definition] do not inline structured filter definitions in ${calleeName}(...). Put them in packages/<crud>/src/shared/<crud>ListFilters.js and import that shared module.`
|
|
529
|
+
);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(firstArgument)) {
|
|
534
|
+
issues.push(
|
|
535
|
+
`${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(...) must receive a definitions symbol imported from a CRUD shared *ListFilters module, not an ad-hoc expression.`
|
|
536
|
+
);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const importSource = importBindings.get(firstArgument) || "";
|
|
541
|
+
if (!isSharedListFiltersImportSource(importSource)) {
|
|
542
|
+
issues.push(
|
|
543
|
+
`${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(${firstArgument}, ...) must use definitions imported from a CRUD shared *ListFilters module. Found ${importSource ? `import source "${importSource}"` : "a local symbol"} instead.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function collectFilterValidatorModeIssues({
|
|
551
|
+
sourceText = "",
|
|
552
|
+
relativePath = "",
|
|
553
|
+
issues = []
|
|
554
|
+
}) {
|
|
555
|
+
for (const callSite of findCallSites(sourceText, "createQueryValidator")) {
|
|
556
|
+
const lineNumber = resolveLineNumberFromIndex(sourceText, callSite.index);
|
|
557
|
+
const argsText = String(callSite.argsText || "").trim();
|
|
558
|
+
if (!argsText.startsWith("{") || !/\binvalidValues\s*:/u.test(argsText)) {
|
|
559
|
+
issues.push(
|
|
560
|
+
`${relativePath}:${lineNumber}: [filters:validator-mode] createQueryValidator(...) must be written explicitly as createQueryValidator({ invalidValues: "reject" | "discard" }). Do not rely on hidden defaults, aliases, or indirect option objects.`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
231
566
|
async function collectMdiSvgDoctorIssues({ appRoot, issues }) {
|
|
232
567
|
if (!(await appUsesVuetifyMdiSvg(appRoot))) {
|
|
233
568
|
return;
|
|
234
569
|
}
|
|
235
570
|
|
|
236
571
|
const vueFilePaths = [];
|
|
237
|
-
for (const relativeRoot of
|
|
238
|
-
await
|
|
572
|
+
for (const relativeRoot of APP_SOURCE_SCAN_ROOTS) {
|
|
573
|
+
await collectAppSourceFiles(
|
|
574
|
+
path.join(appRoot, relativeRoot),
|
|
575
|
+
{ extensions: VUE_SOURCE_EXTENSIONS },
|
|
576
|
+
vueFilePaths
|
|
577
|
+
);
|
|
239
578
|
}
|
|
240
579
|
|
|
241
580
|
vueFilePaths.sort((left, right) => left.localeCompare(right));
|
|
@@ -250,6 +589,38 @@ function createHealthCommands(ctx = {}) {
|
|
|
250
589
|
}
|
|
251
590
|
}
|
|
252
591
|
|
|
592
|
+
async function collectCrudFilterDoctorIssues({ appRoot, issues }) {
|
|
593
|
+
const sourceFilePaths = [];
|
|
594
|
+
for (const relativeRoot of APP_SOURCE_SCAN_ROOTS) {
|
|
595
|
+
await collectAppSourceFiles(path.join(appRoot, relativeRoot), undefined, sourceFilePaths);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
sourceFilePaths.sort((left, right) => left.localeCompare(right));
|
|
599
|
+
|
|
600
|
+
for (const absolutePath of sourceFilePaths) {
|
|
601
|
+
const sourceText = await readFile(absolutePath, "utf8");
|
|
602
|
+
if (
|
|
603
|
+
!sourceText.includes("useCrudListFilters") &&
|
|
604
|
+
!sourceText.includes("createCrudListFilters") &&
|
|
605
|
+
!sourceText.includes("createQueryValidator")
|
|
606
|
+
) {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const relativePath = normalizeRelativePath(appRoot, absolutePath);
|
|
611
|
+
collectFilterDefinitionOwnershipIssues({
|
|
612
|
+
sourceText,
|
|
613
|
+
relativePath,
|
|
614
|
+
issues
|
|
615
|
+
});
|
|
616
|
+
collectFilterValidatorModeIssues({
|
|
617
|
+
sourceText,
|
|
618
|
+
relativePath,
|
|
619
|
+
issues
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
253
624
|
function collectDiLabelParityIssuesForPackage({ packageEntry, packageInsights }) {
|
|
254
625
|
const packageId = String(packageEntry?.packageId || "").trim();
|
|
255
626
|
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
@@ -338,6 +709,10 @@ function createHealthCommands(ctx = {}) {
|
|
|
338
709
|
appRoot,
|
|
339
710
|
issues
|
|
340
711
|
});
|
|
712
|
+
await collectCrudFilterDoctorIssues({
|
|
713
|
+
appRoot,
|
|
714
|
+
issues
|
|
715
|
+
});
|
|
341
716
|
|
|
342
717
|
const payload = {
|
|
343
718
|
appRoot,
|