@dereekb/dbx-cli 13.11.13 → 13.11.15

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.
@@ -0,0 +1,655 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __createRequire } from 'node:module';
3
+ const require = __createRequire(import.meta.url);
4
+
5
+ // packages/dbx-cli/lint-cache/src/main.ts
6
+ import { resolve } from "node:path";
7
+ import yargs from "yargs";
8
+ import { hideBin } from "yargs/helpers";
9
+
10
+ // packages/dbx-cli/lint-cache/src/build.ts
11
+ import { spawn } from "node:child_process";
12
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
13
+ import { join as join3, relative as relative2 } from "node:path";
14
+ import { randomUUID } from "node:crypto";
15
+
16
+ // packages/dbx-cli/lint-cache/src/build-many.ts
17
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
18
+ import { join as join2 } from "node:path";
19
+
20
+ // packages/dbx-cli/lint-cache/src/project-lookup.ts
21
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
22
+ import { join, relative } from "node:path";
23
+ var TOP_LEVEL_DIRS = ["apps", "packages", "tools"];
24
+ var SKIP_DIR_NAMES = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".nx", ".angular", ".next"]);
25
+ function findProject(workspaceRoot, projectName) {
26
+ let result = null;
27
+ for (const dir of TOP_LEVEL_DIRS) {
28
+ if (result) break;
29
+ const base = join(workspaceRoot, dir);
30
+ if (!existsSync(base)) continue;
31
+ result = walkForProject(workspaceRoot, base, projectName);
32
+ }
33
+ if (!result) {
34
+ const rootProject = readProjectJson(join(workspaceRoot, "project.json"));
35
+ if (rootProject?.name === projectName) {
36
+ result = toProjectInfo(workspaceRoot, workspaceRoot, rootProject);
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ function walkForProject(workspaceRoot, dir, projectName) {
42
+ let found = null;
43
+ const entries = readdirSync(dir, { withFileTypes: true });
44
+ for (const e of entries) {
45
+ if (found) break;
46
+ if (!e.isDirectory()) continue;
47
+ if (SKIP_DIR_NAMES.has(e.name) || e.name.startsWith(".")) continue;
48
+ const childDir = join(dir, e.name);
49
+ const pjPath = join(childDir, "project.json");
50
+ if (existsSync(pjPath)) {
51
+ const pj = readProjectJson(pjPath);
52
+ if (pj?.name === projectName) {
53
+ found = toProjectInfo(workspaceRoot, childDir, pj);
54
+ continue;
55
+ }
56
+ }
57
+ found = walkForProject(workspaceRoot, childDir, projectName);
58
+ }
59
+ return found;
60
+ }
61
+ function listProjects(workspaceRoot) {
62
+ const out = [];
63
+ for (const dir of TOP_LEVEL_DIRS) {
64
+ const base = join(workspaceRoot, dir);
65
+ if (!existsSync(base)) continue;
66
+ collectProjects(workspaceRoot, base, out);
67
+ }
68
+ const rootProject = readProjectJson(join(workspaceRoot, "project.json"));
69
+ if (rootProject) out.push(toProjectInfo(workspaceRoot, workspaceRoot, rootProject));
70
+ out.sort((a, b) => a.name.localeCompare(b.name));
71
+ return out;
72
+ }
73
+ function collectProjects(workspaceRoot, dir, out) {
74
+ const entries = readdirSync(dir, { withFileTypes: true });
75
+ for (const e of entries) {
76
+ if (!e.isDirectory()) continue;
77
+ if (SKIP_DIR_NAMES.has(e.name) || e.name.startsWith(".") || e.name === "src") continue;
78
+ const childDir = join(dir, e.name);
79
+ const pjPath = join(childDir, "project.json");
80
+ if (existsSync(pjPath)) {
81
+ const pj = readProjectJson(pjPath);
82
+ if (pj) out.push(toProjectInfo(workspaceRoot, childDir, pj));
83
+ }
84
+ collectProjects(workspaceRoot, childDir, out);
85
+ }
86
+ }
87
+ function readProjectJson(path) {
88
+ let parsed;
89
+ try {
90
+ parsed = JSON.parse(readFileSync(path, "utf8"));
91
+ } catch {
92
+ parsed = null;
93
+ }
94
+ return parsed;
95
+ }
96
+ function toProjectInfo(workspaceRoot, projectRoot, pj) {
97
+ const lintTarget = pj.targets?.["lint"];
98
+ const lintPatterns = lintTarget?.options?.lintFilePatterns;
99
+ return {
100
+ name: pj.name ?? "",
101
+ projectRoot: relative(workspaceRoot, projectRoot) || ".",
102
+ absoluteRoot: projectRoot,
103
+ lintFilePatterns: Array.isArray(lintPatterns) && lintPatterns.length > 0 ? lintPatterns : void 0,
104
+ hasLintTarget: lintTarget != null
105
+ };
106
+ }
107
+
108
+ // packages/dbx-cli/lint-cache/src/build-many.ts
109
+ async function runBuildMany(opts) {
110
+ const lintable = listProjects(opts.workspaceRoot).filter((p) => p.hasLintTarget);
111
+ const targets = filterProjects({ projects: lintable, include: opts.include, exclude: opts.exclude });
112
+ if (!existsSync2(opts.outputDir)) mkdirSync(opts.outputDir, { recursive: true });
113
+ const results = [];
114
+ const queue = [...targets];
115
+ const total = targets.length;
116
+ let started = 0;
117
+ const worker = async () => {
118
+ while (queue.length > 0) {
119
+ const project = queue.shift();
120
+ if (!project) return;
121
+ started += 1;
122
+ const index = started;
123
+ opts.onProgress?.({ kind: "start", project: project.name, index, total });
124
+ try {
125
+ const { cachePath, cache } = await runBuild({
126
+ project: project.name,
127
+ workspaceRoot: opts.workspaceRoot,
128
+ outputDir: opts.outputDir,
129
+ nxArgs: opts.nxArgs,
130
+ fix: opts.fix,
131
+ updateIndex: false
132
+ });
133
+ results.push(projectResultFromCache({ project: project.name, cachePath, cache }));
134
+ opts.onProgress?.({ kind: "done", project: project.name, index, total, cache });
135
+ } catch (e) {
136
+ const message = e instanceof Error ? e.message : String(e);
137
+ results.push({
138
+ project: project.name,
139
+ cachePath: void 0,
140
+ errorCount: void 0,
141
+ warningCount: void 0,
142
+ filesWithIssues: void 0,
143
+ generatedAt: void 0,
144
+ error: message
145
+ });
146
+ opts.onProgress?.({ kind: "error", project: project.name, index, total, error: message });
147
+ if (!opts.continueOnError) throw e;
148
+ }
149
+ }
150
+ };
151
+ const pool = Array.from({ length: Math.max(1, opts.concurrency) }, () => worker());
152
+ await Promise.all(pool);
153
+ results.sort((a, b) => a.project.localeCompare(b.project));
154
+ const totalErrors = results.reduce((acc, r) => acc + (r.errorCount ?? 0), 0);
155
+ const totalWarnings = results.reduce((acc, r) => acc + (r.warningCount ?? 0), 0);
156
+ const indexPath = join2(opts.outputDir, "index.json");
157
+ writeFileSync(
158
+ indexPath,
159
+ JSON.stringify(
160
+ {
161
+ schemaVersion: 1,
162
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
163
+ projectCount: results.length,
164
+ succeeded: results.filter((r) => r.error == null).length,
165
+ failed: results.filter((r) => r.error != null).length,
166
+ totalErrors,
167
+ totalWarnings,
168
+ projects: results
169
+ },
170
+ null,
171
+ 2
172
+ )
173
+ );
174
+ return { indexPath, projects: results, totalErrors, totalWarnings };
175
+ }
176
+ function filterProjects(input) {
177
+ const includeMatchers = input.include.map(matcherFor);
178
+ const excludeMatchers = input.exclude.map(matcherFor);
179
+ return input.projects.filter((p) => {
180
+ let keep = true;
181
+ if (includeMatchers.length > 0 && !includeMatchers.some((m) => m(p.name))) keep = false;
182
+ if (keep && excludeMatchers.some((m) => m(p.name))) keep = false;
183
+ return keep;
184
+ });
185
+ }
186
+ function matcherFor(pattern) {
187
+ let result;
188
+ if (!pattern.includes("*") && !pattern.includes("?")) {
189
+ result = (s) => s.includes(pattern);
190
+ } else {
191
+ const re = globToRegExp(pattern);
192
+ result = (s) => re.test(s);
193
+ }
194
+ return result;
195
+ }
196
+ function globToRegExp(pattern) {
197
+ let regex = "";
198
+ let i = 0;
199
+ while (i < pattern.length) {
200
+ const ch = pattern[i];
201
+ if (ch === "*") {
202
+ regex += ".*";
203
+ i += 1;
204
+ } else if (ch === "?") {
205
+ regex += ".";
206
+ i += 1;
207
+ } else if (String.raw`.+^$()|[]{}\/`.includes(ch)) {
208
+ regex += String.raw`\${ch}`;
209
+ i += 1;
210
+ } else {
211
+ regex += ch;
212
+ i += 1;
213
+ }
214
+ }
215
+ return new RegExp(`^${regex}$`);
216
+ }
217
+ function projectResultFromCache(input) {
218
+ return {
219
+ project: input.project,
220
+ cachePath: input.cachePath,
221
+ errorCount: input.cache.errorCount,
222
+ warningCount: input.cache.warningCount,
223
+ filesWithIssues: input.cache.filesWithIssues,
224
+ generatedAt: input.cache.generatedAt,
225
+ error: void 0
226
+ };
227
+ }
228
+ function patchIndexEntry(input) {
229
+ const indexPath = join2(input.outputDir, "index.json");
230
+ let patched = false;
231
+ if (existsSync2(indexPath)) {
232
+ const index = JSON.parse(readFileSync2(indexPath, "utf8"));
233
+ const projects = [...index.projects];
234
+ const existingIdx = projects.findIndex((p) => p.project === input.entry.project);
235
+ if (existingIdx >= 0) {
236
+ projects[existingIdx] = input.entry;
237
+ } else {
238
+ projects.push(input.entry);
239
+ projects.sort((a, b) => a.project.localeCompare(b.project));
240
+ }
241
+ const next = {
242
+ schemaVersion: 1,
243
+ generatedAt: index.generatedAt,
244
+ projectCount: projects.length,
245
+ succeeded: projects.filter((p) => p.error == null).length,
246
+ failed: projects.filter((p) => p.error != null).length,
247
+ totalErrors: projects.reduce((acc, r) => acc + (r.errorCount ?? 0), 0),
248
+ totalWarnings: projects.reduce((acc, r) => acc + (r.warningCount ?? 0), 0),
249
+ projects
250
+ };
251
+ writeFileSync(indexPath, JSON.stringify(next, null, 2));
252
+ patched = true;
253
+ }
254
+ return patched;
255
+ }
256
+
257
+ // packages/dbx-cli/lint-cache/src/types.ts
258
+ function cacheFileName(projectName) {
259
+ return `${projectName.replaceAll(/[^A-Za-z0-9._-]/g, "_")}.json`;
260
+ }
261
+
262
+ // packages/dbx-cli/lint-cache/src/build.ts
263
+ async function runBuild(opts) {
264
+ const project = findProject(opts.workspaceRoot, opts.project);
265
+ if (!project) {
266
+ throw new Error(`project not found in workspace: ${opts.project}`);
267
+ }
268
+ if (!existsSync3(opts.outputDir)) mkdirSync2(opts.outputDir, { recursive: true });
269
+ const tmpFile = join3(opts.outputDir, `.tmp-${randomUUID()}.json`);
270
+ const tmpFileRel = relative2(opts.workspaceRoot, tmpFile);
271
+ let raw;
272
+ try {
273
+ await spawnNxLint({
274
+ workspaceRoot: opts.workspaceRoot,
275
+ project: opts.project,
276
+ outputFile: tmpFileRel,
277
+ fix: opts.fix,
278
+ extraArgs: opts.nxArgs ?? []
279
+ });
280
+ if (!existsSync3(tmpFile)) {
281
+ throw new Error(`nx lint did not write the expected JSON output to ${tmpFile}`);
282
+ }
283
+ raw = JSON.parse(readFileSync3(tmpFile, "utf8"));
284
+ } finally {
285
+ if (existsSync3(tmpFile)) rmSync(tmpFile, { force: true });
286
+ }
287
+ const cache = buildCache({
288
+ raw,
289
+ project: opts.project,
290
+ projectRoot: project.projectRoot,
291
+ workspaceRoot: opts.workspaceRoot
292
+ });
293
+ const cachePath = join3(opts.outputDir, cacheFileName(opts.project));
294
+ writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
295
+ if (opts.updateIndex !== false) {
296
+ patchIndexEntry({
297
+ outputDir: opts.outputDir,
298
+ entry: projectResultFromCache({ project: opts.project, cachePath, cache })
299
+ });
300
+ }
301
+ return { cachePath, cache };
302
+ }
303
+ function spawnNxLint(opts) {
304
+ const fixArgs = opts.fix ? ["--fix"] : [];
305
+ const args = ["nx", "run", `${opts.project}:lint`, "--format=json", `--output-file=${opts.outputFile}`, "--silent", "--no-cloud", ...fixArgs, ...opts.extraArgs];
306
+ return new Promise((resolvePromise, rejectPromise) => {
307
+ const child = spawn("npx", args, {
308
+ cwd: opts.workspaceRoot,
309
+ env: { ...process.env, FORCE_COLOR: "0" },
310
+ stdio: ["ignore", "inherit", "inherit"]
311
+ });
312
+ child.on("error", rejectPromise);
313
+ child.on("exit", () => {
314
+ resolvePromise();
315
+ });
316
+ });
317
+ }
318
+ function buildCache(input) {
319
+ const messages = [];
320
+ const fileSummariesMap = /* @__PURE__ */ new Map();
321
+ const ruleSummariesMap = /* @__PURE__ */ new Map();
322
+ let errorCount = 0;
323
+ let warningCount = 0;
324
+ let fixableErrorCount = 0;
325
+ let fixableWarningCount = 0;
326
+ let filesWithIssues = 0;
327
+ for (const r of input.raw) {
328
+ if (r.messages.length === 0) continue;
329
+ const filePath = relative2(input.workspaceRoot, r.filePath) || r.filePath;
330
+ filesWithIssues += 1;
331
+ fileSummariesMap.set(filePath, { errors: r.errorCount, warnings: r.warningCount });
332
+ errorCount += r.errorCount;
333
+ warningCount += r.warningCount;
334
+ fixableErrorCount += r.fixableErrorCount;
335
+ fixableWarningCount += r.fixableWarningCount;
336
+ for (const m of r.messages) {
337
+ const sev = m.severity === 2 ? "error" : "warning";
338
+ messages.push({
339
+ filePath,
340
+ line: m.line ?? 0,
341
+ column: m.column ?? 0,
342
+ endLine: m.endLine ?? null,
343
+ endColumn: m.endColumn ?? null,
344
+ ruleId: m.ruleId ?? null,
345
+ severity: sev,
346
+ message: m.message,
347
+ fixable: m.fix != null
348
+ });
349
+ const ruleKey = m.ruleId ?? "(no-rule)";
350
+ let entry = ruleSummariesMap.get(ruleKey);
351
+ if (!entry) {
352
+ entry = { errors: 0, warnings: 0, files: /* @__PURE__ */ new Set() };
353
+ ruleSummariesMap.set(ruleKey, entry);
354
+ }
355
+ if (sev === "error") entry.errors += 1;
356
+ else entry.warnings += 1;
357
+ entry.files.add(filePath);
358
+ }
359
+ }
360
+ const ruleSummaries = Array.from(ruleSummariesMap.entries()).map(([rule, v]) => ({ rule, errors: v.errors, warnings: v.warnings, files: v.files.size })).sort((a, b) => b.errors + b.warnings - (a.errors + a.warnings) || a.rule.localeCompare(b.rule));
361
+ const fileSummaries = Array.from(fileSummariesMap.entries()).map(([filePath, v]) => ({ filePath, errors: v.errors, warnings: v.warnings })).sort((a, b) => b.errors + b.warnings - (a.errors + a.warnings) || a.filePath.localeCompare(b.filePath));
362
+ return {
363
+ schemaVersion: 1,
364
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
365
+ project: input.project,
366
+ projectRoot: input.projectRoot,
367
+ eslintVersion: "nx-lint-executor",
368
+ errorCount,
369
+ warningCount,
370
+ fixableErrorCount,
371
+ fixableWarningCount,
372
+ fileCount: input.raw.length,
373
+ filesWithIssues,
374
+ ruleSummaries,
375
+ fileSummaries,
376
+ messages
377
+ };
378
+ }
379
+
380
+ // packages/dbx-cli/lint-cache/src/format.ts
381
+ function renderResult(format, result) {
382
+ let output;
383
+ switch (format) {
384
+ case "rules":
385
+ output = renderRules(result);
386
+ break;
387
+ case "files":
388
+ output = renderFiles(result);
389
+ break;
390
+ case "messages":
391
+ output = renderMessages(result);
392
+ break;
393
+ case "json":
394
+ output = JSON.stringify(result.matched, null, 2);
395
+ break;
396
+ default:
397
+ output = renderSummary(result);
398
+ }
399
+ return output;
400
+ }
401
+ function renderSummary(r) {
402
+ const c = r.cache;
403
+ const lines = [];
404
+ lines.push(`Project: ${c.project} (${c.projectRoot})`, `Generated: ${c.generatedAt}`, `Totals: ${c.errorCount} errors \xB7 ${c.warningCount} warnings \xB7 ${c.filesWithIssues}/${c.fileCount} files with issues`);
405
+ const truncatedSuffix = r.truncated ? ` (showing ${r.matched.length})` : "";
406
+ lines.push(`Matched: ${r.totalMatched} messages${truncatedSuffix}`);
407
+ if (r.matched.length > 0) {
408
+ const ruleCounts = aggregateByRule(r.matched);
409
+ const fileCounts = aggregateByFile(r.matched);
410
+ lines.push("", "Top rules:");
411
+ for (const [rule, count] of topEntries(ruleCounts, 10)) {
412
+ lines.push(` ${pad(count, 5)} ${rule}`);
413
+ }
414
+ lines.push("", "Top files:");
415
+ for (const [file, count] of topEntries(fileCounts, 10)) {
416
+ lines.push(` ${pad(count, 5)} ${file}`);
417
+ }
418
+ }
419
+ return lines.join("\n");
420
+ }
421
+ function renderRules(r) {
422
+ const map = /* @__PURE__ */ new Map();
423
+ for (const m of r.matched) {
424
+ const rule = m.ruleId ?? "(no-rule)";
425
+ let entry = map.get(rule);
426
+ if (!entry) {
427
+ entry = { errors: 0, warnings: 0, files: /* @__PURE__ */ new Set() };
428
+ map.set(rule, entry);
429
+ }
430
+ if (m.severity === "error") entry.errors += 1;
431
+ else entry.warnings += 1;
432
+ entry.files.add(m.filePath);
433
+ }
434
+ const sorted = Array.from(map.entries()).sort((a, b) => b[1].errors + b[1].warnings - (a[1].errors + a[1].warnings));
435
+ const lines = [`Rules (${sorted.length}):`];
436
+ for (const [rule, v] of sorted) {
437
+ lines.push(` ${pad(v.errors + v.warnings, 5)} ${rule} (${v.errors} err / ${v.warnings} warn, ${v.files.size} files)`);
438
+ }
439
+ return lines.join("\n");
440
+ }
441
+ function renderFiles(r) {
442
+ const map = /* @__PURE__ */ new Map();
443
+ for (const m of r.matched) {
444
+ let entry = map.get(m.filePath);
445
+ if (!entry) {
446
+ entry = { errors: 0, warnings: 0 };
447
+ map.set(m.filePath, entry);
448
+ }
449
+ if (m.severity === "error") entry.errors += 1;
450
+ else entry.warnings += 1;
451
+ }
452
+ const sorted = Array.from(map.entries()).sort((a, b) => b[1].errors + b[1].warnings - (a[1].errors + a[1].warnings));
453
+ const lines = [`Files (${sorted.length}):`];
454
+ for (const [file, v] of sorted) {
455
+ lines.push(` ${pad(v.errors + v.warnings, 5)} ${file} (${v.errors} err / ${v.warnings} warn)`);
456
+ }
457
+ return lines.join("\n");
458
+ }
459
+ function renderMessages(r) {
460
+ const truncatedSuffix = r.truncated ? `, showing ${r.matched.length}` : "";
461
+ const lines = [`Messages (${r.totalMatched}${truncatedSuffix}):`];
462
+ for (const m of r.matched) {
463
+ const sev = m.severity === "error" ? "ERR " : "WARN";
464
+ lines.push(` ${sev} ${m.filePath}:${m.line}:${m.column} [${m.ruleId ?? "(no-rule)"}] ${m.message}`);
465
+ }
466
+ return lines.join("\n");
467
+ }
468
+ function aggregateByRule(messages) {
469
+ const counts = /* @__PURE__ */ new Map();
470
+ for (const m of messages) {
471
+ const key = m.ruleId ?? "(no-rule)";
472
+ counts.set(key, (counts.get(key) ?? 0) + 1);
473
+ }
474
+ return counts;
475
+ }
476
+ function aggregateByFile(messages) {
477
+ const counts = /* @__PURE__ */ new Map();
478
+ for (const m of messages) {
479
+ counts.set(m.filePath, (counts.get(m.filePath) ?? 0) + 1);
480
+ }
481
+ return counts;
482
+ }
483
+ function topEntries(counts, n) {
484
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, n);
485
+ }
486
+ function pad(n, width) {
487
+ const s = String(n);
488
+ return s.length >= width ? s : " ".repeat(width - s.length) + s;
489
+ }
490
+
491
+ // packages/dbx-cli/lint-cache/src/query.ts
492
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
493
+ function runQuery(cachePath, filters) {
494
+ if (!existsSync4(cachePath)) {
495
+ throw new Error(`lint cache not found: ${cachePath} \u2014 run \`build\` first`);
496
+ }
497
+ const cache = JSON.parse(readFileSync4(cachePath, "utf8"));
498
+ const ruleSet = filters.rule && filters.rule.length > 0 ? new Set(filters.rule) : null;
499
+ const fileNeedle = filters.file && filters.file.length > 0 ? filters.file : null;
500
+ const fileMatcher = fileNeedle && isGlobPattern(fileNeedle) ? globToRegExp2(fileNeedle) : null;
501
+ const messageNeedle = filters.message && filters.message.length > 0 ? filters.message.toLowerCase() : null;
502
+ const severity = filters.severity;
503
+ const matched = [];
504
+ for (const m of cache.messages) {
505
+ if (ruleSet && (m.ruleId == null || !ruleSet.has(m.ruleId))) continue;
506
+ if (severity && m.severity !== severity) continue;
507
+ if (fileNeedle) {
508
+ if (fileMatcher) {
509
+ if (!fileMatcher.test(m.filePath)) continue;
510
+ } else if (!m.filePath.includes(fileNeedle)) {
511
+ continue;
512
+ }
513
+ }
514
+ if (messageNeedle && !m.message.toLowerCase().includes(messageNeedle)) continue;
515
+ matched.push(m);
516
+ }
517
+ const totalMatched = matched.length;
518
+ const truncated = filters.limit != null && totalMatched > filters.limit;
519
+ const limited = filters.limit == null ? matched : matched.slice(0, filters.limit);
520
+ return { cache, matched: limited, totalMatched, truncated };
521
+ }
522
+ function isGlobPattern(s) {
523
+ return s.includes("*") || s.includes("?");
524
+ }
525
+ function globToRegExp2(pattern) {
526
+ let regex = "";
527
+ let i = 0;
528
+ while (i < pattern.length) {
529
+ const ch = pattern[i];
530
+ if (ch === "*" && pattern[i + 1] === "*") {
531
+ regex += ".*";
532
+ i += 2;
533
+ if (pattern[i] === "/") i += 1;
534
+ } else if (ch === "*") {
535
+ regex += "[^/]*";
536
+ i += 1;
537
+ } else if (ch === "?") {
538
+ regex += "[^/]";
539
+ i += 1;
540
+ } else if (String.raw`.+^$()|[]{}\/`.includes(ch)) {
541
+ regex += String.raw`\${ch}`;
542
+ i += 1;
543
+ } else {
544
+ regex += ch;
545
+ i += 1;
546
+ }
547
+ }
548
+ return new RegExp(`^${regex}$`);
549
+ }
550
+
551
+ // packages/dbx-cli/lint-cache/src/main.ts
552
+ var FORMAT_CHOICES = ["summary", "rules", "files", "messages", "json"];
553
+ var SEVERITY_CHOICES = ["error", "warning"];
554
+ await yargs(hideBin(process.argv)).scriptName("dbx-cli-lint-cache").usage("$0 <command> [options]").command(
555
+ "build <project>",
556
+ "Run ESLint for a project and write the grouped result cache.",
557
+ (y) => y.positional("project", { type: "string", describe: "Nx project name", demandOption: true }).option("output-dir", { type: "string", default: ".tmp/lint-cache", describe: "Directory the cache file is written into (workspace-relative)." }).option("workspace", { type: "string", describe: "Workspace root (defaults to cwd)." }).option("nx-arg", { type: "array", string: true, describe: "Extra argument passed through to `nx run <project>:lint` (repeatable)." }).option("fix", { type: "boolean", default: false, describe: "Run ESLint with --fix; remaining (non-fixable) issues are still cached." }).option("quiet", { type: "boolean", default: false, describe: "Suppress the post-build summary line." }),
558
+ async (args) => {
559
+ const workspaceRoot = resolveWorkspaceRoot(args);
560
+ const outputDir = resolve(workspaceRoot, args["output-dir"]);
561
+ const { cachePath, cache } = await runBuild({
562
+ project: args.project,
563
+ workspaceRoot,
564
+ outputDir,
565
+ nxArgs: args["nx-arg"],
566
+ fix: args.fix
567
+ });
568
+ if (!args.quiet) {
569
+ console.log(`[wrote] ${cachePath}`);
570
+ console.log(`Summary: ${cache.errorCount} errors \xB7 ${cache.warningCount} warnings \xB7 ${cache.filesWithIssues}/${cache.fileCount} files with issues${args.fix ? " (after --fix)" : ""}`);
571
+ }
572
+ }
573
+ ).command(
574
+ "query <project>",
575
+ "Filter and display messages from a project's cached lint result.",
576
+ (y) => y.positional("project", { type: "string", describe: "Nx project name", demandOption: true }).option("cache-dir", { type: "string", default: ".tmp/lint-cache", describe: "Directory the cache file lives in (workspace-relative)." }).option("workspace", { type: "string", describe: "Workspace root (defaults to cwd)." }).option("rule", { type: "array", string: true, describe: "Filter to one or more rule IDs (repeatable). OR-ed." }).option("severity", { choices: SEVERITY_CHOICES, describe: "Filter to errors or warnings only." }).option("file", { type: "string", describe: "Substring filter against the file path; supports * and ** glob chars." }).option("message", { type: "string", describe: "Substring filter against the message text (case-insensitive)." }).option("limit", { type: "number", describe: "Limit the printed messages slice (totalMatched still reflects the full match count)." }).option("format", { choices: FORMAT_CHOICES, default: "summary", describe: "Output format." }),
577
+ (args) => {
578
+ const workspaceRoot = resolveWorkspaceRoot(args);
579
+ const cacheDir = resolve(workspaceRoot, args["cache-dir"]);
580
+ const cachePath = resolve(cacheDir, cacheFileName(args.project));
581
+ const result = runQuery(cachePath, {
582
+ rule: args.rule,
583
+ severity: args.severity,
584
+ file: args.file,
585
+ message: args.message,
586
+ limit: args.limit
587
+ });
588
+ console.log(renderResult(args.format, result));
589
+ }
590
+ ).command(
591
+ "build-many",
592
+ "Run ESLint for every project with a lint target (with optional filters) and write per-project caches plus an aggregate index.json.",
593
+ (y) => y.option("output-dir", { type: "string", default: ".tmp/lint-cache", describe: "Directory the cache files are written into (workspace-relative)." }).option("workspace", { type: "string", describe: "Workspace root (defaults to cwd)." }).option("include", { type: "array", string: true, default: [], describe: "Only build projects whose name matches one of these patterns (substring or *-glob). Repeatable." }).option("exclude", { type: "array", string: true, default: [], describe: "Skip projects whose name matches one of these patterns (substring or *-glob). Repeatable." }).option("concurrency", { type: "number", default: 4, describe: "Number of `nx run <p>:lint` processes to run in parallel." }).option("continue-on-error", { type: "boolean", default: true, describe: "Continue past per-project failures and record them in index.json." }).option("nx-arg", { type: "array", string: true, describe: "Extra argument passed through to each `nx run <project>:lint` (repeatable)." }).option("fix", { type: "boolean", default: false, describe: "Run ESLint with --fix on every targeted project; remaining (non-fixable) issues are still cached." }).option("quiet", { type: "boolean", default: false, describe: "Suppress per-project progress output." }),
594
+ async (args) => {
595
+ const workspaceRoot = resolveWorkspaceRoot(args);
596
+ const outputDir = resolve(workspaceRoot, args["output-dir"]);
597
+ const include = args.include ?? [];
598
+ const exclude = args.exclude ?? [];
599
+ const result = await runBuildMany({
600
+ workspaceRoot,
601
+ outputDir,
602
+ include,
603
+ exclude,
604
+ concurrency: args.concurrency,
605
+ continueOnError: args["continue-on-error"],
606
+ nxArgs: args["nx-arg"],
607
+ fix: args.fix,
608
+ onProgress: args.quiet ? void 0 : printProgress
609
+ });
610
+ if (!args.quiet) {
611
+ const failed = result.projects.filter((p) => p.error != null).length;
612
+ console.log("");
613
+ console.log(`[wrote] ${result.indexPath}`);
614
+ console.log(`Totals: ${result.totalErrors} errors \xB7 ${result.totalWarnings} warnings across ${result.projects.length} projects (${failed} failed).`);
615
+ }
616
+ }
617
+ ).command(
618
+ "list-projects",
619
+ "List Nx projects that build-many would target after applying include/exclude filters.",
620
+ (y) => y.option("workspace", { type: "string", describe: "Workspace root (defaults to cwd)." }).option("include", { type: "array", string: true, default: [], describe: "Substring or *-glob filter to keep projects. Repeatable." }).option("exclude", { type: "array", string: true, default: [], describe: "Substring or *-glob filter to drop projects. Repeatable." }).option("all", { type: "boolean", default: false, describe: "Include projects that do not declare a lint target." }),
621
+ (args) => {
622
+ const workspaceRoot = resolveWorkspaceRoot(args);
623
+ const include = args.include ?? [];
624
+ const exclude = args.exclude ?? [];
625
+ const projects = listProjects(workspaceRoot);
626
+ const lintable = args.all ? projects : projects.filter((p) => p.hasLintTarget);
627
+ const filtered = filterProjects({ projects: lintable, include, exclude });
628
+ for (const p of filtered) {
629
+ const lintFlag = p.hasLintTarget ? "" : " (no lint target)";
630
+ console.log(`${p.name} ${p.projectRoot}${lintFlag}`);
631
+ }
632
+ console.log(`# ${filtered.length} project(s) (of ${lintable.length} lintable, ${projects.length} total)`);
633
+ }
634
+ ).demandCommand(1, "Specify a command (build, build-many, query, list-projects).").strict().help().alias("h", "help").parseAsync().catch((e) => {
635
+ const message = e instanceof Error ? e.message : String(e);
636
+ console.error(message);
637
+ process.exit(1);
638
+ });
639
+ function resolveWorkspaceRoot(args) {
640
+ return resolve(args.workspace ?? process.cwd());
641
+ }
642
+ function printProgress(event) {
643
+ const tag = `[${event.index}/${event.total}]`;
644
+ switch (event.kind) {
645
+ case "start":
646
+ console.log(`${tag} ${event.project} ...`);
647
+ break;
648
+ case "done":
649
+ console.log(`${tag} ${event.project} \u2192 ${event.cache.errorCount} err / ${event.cache.warningCount} warn (${event.cache.filesWithIssues}/${event.cache.fileCount} files)`);
650
+ break;
651
+ case "error":
652
+ console.error(`${tag} ${event.project} FAILED: ${event.error}`);
653
+ break;
654
+ }
655
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@dereekb/dbx-cli-lint-cache",
3
+ "version": "13.11.15",
4
+ "private": true,
5
+ "type": "module",
6
+ "devDependencies": {
7
+ "@dereekb/util": "13.11.15",
8
+ "eslint": "10.4.0",
9
+ "yargs": "^18.0.0",
10
+ "@types/yargs": "^17.0.35"
11
+ }
12
+ }