@svelte-vitals/core 0.22.1 → 0.23.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/dist/index.d.ts CHANGED
@@ -629,6 +629,10 @@ interface ConsoleReportOptions {
629
629
  mode?: string;
630
630
  /** Color decorators; defaults to no color. */
631
631
  palette?: Palette;
632
+ /** Show every failing/passed/route entry uncapped and ungrouped, exactly as before this option existed. Default false (capped, grouped by rule). */
633
+ verbose?: boolean;
634
+ /** Internal: set by the CLI when it has already animated the Health header itself — skips the brand/Health lines (category score lines still print). Default false. */
635
+ omitHeader?: boolean;
632
636
  }
633
637
  /**
634
638
  * Render results as a console report string (design §7). Pure: returns a string,
package/dist/index.js CHANGED
@@ -2399,27 +2399,53 @@ var CATEGORY_LABEL = {
2399
2399
  architecture: "Architecture"
2400
2400
  };
2401
2401
  var CATEGORY_ORDER = ["seo", "performance", "correctness", "security", "architecture"];
2402
+ var MAX_RULE_GROUPS_PER_BUCKET = 5;
2403
+ function groupByRule(results) {
2404
+ const groups = /* @__PURE__ */ new Map();
2405
+ for (const r of results) {
2406
+ const bucket = groups.get(r.id);
2407
+ if (bucket) bucket.push(r);
2408
+ else groups.set(r.id, [r]);
2409
+ }
2410
+ return [...groups.entries()].map(([id, rs]) => ({ id, results: rs })).sort((a, b) => b.results.length - a.results.length || a.id.localeCompare(b.id));
2411
+ }
2402
2412
  function scoreLine(p, label, { score, scoreModel }) {
2403
2413
  const parts = [`route avg ${scoreModel.routeAverage}`];
2404
2414
  if (scoreModel.sitePenalty > 0) parts.push(`site \u2212${scoreModel.sitePenalty}`);
2405
2415
  if (scoreModel.criticalCap !== null) parts.push(`capped at ${scoreModel.criticalCap}: critical present`);
2406
2416
  return `${label} Score: ${scoreColor(p, score)(`${score}/100`)} ${p.dim(`(${parts.join(" \xB7 ")})`)}`;
2407
2417
  }
2408
- function byRouteTree(p, results, config) {
2418
+ var MAX_ROUTES_BY_ROUTE = 10;
2419
+ function byRouteTree(p, results, config, verbose) {
2409
2420
  const routes = /* @__PURE__ */ new Map();
2410
2421
  for (const r of results) {
2411
2422
  if (r.route === void 0) continue;
2412
2423
  if (!routes.has(r.route)) routes.set(r.route, []);
2413
2424
  routes.get(r.route).push(r);
2414
2425
  }
2426
+ const scored = [...routes.entries()].map(([route, rs]) => ({
2427
+ route,
2428
+ rs,
2429
+ score: computeScore(rs, config, { applyCriticalCap: false }).score
2430
+ }));
2431
+ scored.sort((a, b) => a.score - b.score || a.route.localeCompare(b.route));
2432
+ const shown = verbose ? scored : scored.slice(0, MAX_ROUTES_BY_ROUTE);
2415
2433
  const lines = [p.bold("By route"), p.dim(RULE)];
2416
- for (const [route, rs] of [...routes.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
2417
- const { score } = computeScore(rs, config, { applyCriticalCap: false });
2434
+ for (const { route, rs, score } of shown) {
2418
2435
  lines.push(`${route.padEnd(28)} ${scoreColor(p, score)(`${score}`)}`);
2419
2436
  for (const r of rs.filter((x) => classify(x, config) === "fail")) {
2420
2437
  lines.push(` ${p.red("\u2717")} ${r.id} ${r.message}`);
2421
2438
  }
2422
2439
  }
2440
+ if (!verbose && scored.length > MAX_ROUTES_BY_ROUTE) {
2441
+ const remaining = scored.slice(MAX_ROUTES_BY_ROUTE);
2442
+ const avgScore = Math.round(remaining.reduce((sum, r) => sum + r.score, 0) / remaining.length);
2443
+ lines.push(
2444
+ p.dim(
2445
+ `\u2026and ${remaining.length} more route${remaining.length > 1 ? "s" : ""} (avg score ${avgScore}) \u2014 run with --verbose to see all`
2446
+ )
2447
+ );
2448
+ }
2423
2449
  lines.push("");
2424
2450
  return lines;
2425
2451
  }
@@ -2428,15 +2454,18 @@ function formatConsoleReport(results, config, options = {}) {
2428
2454
  const summary = summarize(results, config);
2429
2455
  const { health, categories: byCat } = computeHealth(results, config);
2430
2456
  const present2 = CATEGORY_ORDER.filter((c) => byCat[c] !== void 0);
2431
- const header = [
2432
- p.bold(`Svelte Vitals \xB7 ${options.mode ?? "static mode"}`),
2433
- "",
2434
- `${p.bold("Health:")} ${scoreColor(p, health)(`${health}/100`)}`
2435
- ];
2457
+ const lines = [];
2458
+ if (!options.omitHeader) {
2459
+ lines.push(
2460
+ p.bold(`Svelte Vitals \xB7 ${options.mode ?? "static mode"}`),
2461
+ "",
2462
+ `${p.bold("Health:")} ${scoreColor(p, health)(`${health}/100`)}`
2463
+ );
2464
+ }
2436
2465
  for (const c of present2) {
2437
- header.push(scoreLine(p, CATEGORY_LABEL[c] ?? c, byCat[c]));
2466
+ lines.push(scoreLine(p, CATEGORY_LABEL[c] ?? c, byCat[c]));
2438
2467
  }
2439
- const lines = [...header, ""];
2468
+ lines.push("");
2440
2469
  const SEVERITY_COLOR = {
2441
2470
  critical: (s) => p.red(p.bold(s)),
2442
2471
  warning: (s) => p.yellow(p.bold(s)),
@@ -2447,24 +2476,46 @@ function formatConsoleReport(results, config, options = {}) {
2447
2476
  const bucket = failures.filter((r) => effectiveSeverity(r, config) === severity);
2448
2477
  if (bucket.length === 0) continue;
2449
2478
  lines.push(SEVERITY_COLOR[severity](`${SEVERITY_TITLE[severity]} (${bucket.length})`), p.dim(RULE));
2450
- for (const r of bucket) {
2451
- lines.push(`${p.red("\u2717")} ${r.id} ${r.message}`);
2452
- if (r.route) lines.push(p.dim(` ${r.route}`));
2453
- if (r.location) lines.push(p.dim(` ${r.location}${r.line ? `:${r.line}` : ""}`));
2479
+ if (options.verbose) {
2480
+ for (const r of bucket) {
2481
+ lines.push(`${p.red("\u2717")} ${r.id} ${r.message}`);
2482
+ if (r.route) lines.push(p.dim(` ${r.route}`));
2483
+ if (r.location) lines.push(p.dim(` ${r.location}${r.line ? `:${r.line}` : ""}`));
2484
+ }
2485
+ } else {
2486
+ const groups = groupByRule(bucket);
2487
+ const shownGroups = groups.slice(0, MAX_RULE_GROUPS_PER_BUCKET);
2488
+ for (const group of shownGroups) {
2489
+ const r = group.results[0];
2490
+ lines.push(`${p.red("\u2717")} ${r.id} ${r.message}`);
2491
+ if (r.route) lines.push(p.dim(` ${r.route}`));
2492
+ if (r.location) lines.push(p.dim(` ${r.location}${r.line ? `:${r.line}` : ""}`));
2493
+ if (group.results.length > 1) {
2494
+ lines.push(p.dim(` \u2026and ${group.results.length - 1} more`));
2495
+ }
2496
+ }
2497
+ if (groups.length > MAX_RULE_GROUPS_PER_BUCKET) {
2498
+ const remaining = groups.length - MAX_RULE_GROUPS_PER_BUCKET;
2499
+ lines.push(
2500
+ p.dim(`\u2026and ${remaining} more rule${remaining > 1 ? "s" : ""} affected \u2014 run with --verbose to see all`)
2501
+ );
2502
+ }
2454
2503
  }
2455
2504
  lines.push("");
2456
2505
  }
2457
2506
  const passed = results.filter((r) => classify(r, config) !== "fail");
2458
2507
  if (passed.length > 0) {
2459
2508
  lines.push(p.bold(`Passed (${passed.length})`), p.dim(RULE));
2460
- for (const r of passed) {
2461
- const marker = classify(r, config) === "dynamic" ? p.cyan(" \u21AF dynamic") : "";
2462
- const route = r.route ? ` ${r.route}` : "";
2463
- lines.push(`${p.green("\u2713")} ${r.id} ${r.message}${marker}${route}`);
2509
+ if (options.verbose) {
2510
+ for (const r of passed) {
2511
+ const marker = classify(r, config) === "dynamic" ? p.cyan(" \u21AF dynamic") : "";
2512
+ const route = r.route ? ` ${r.route}` : "";
2513
+ lines.push(`${p.green("\u2713")} ${r.id} ${r.message}${marker}${route}`);
2514
+ }
2464
2515
  }
2465
2516
  lines.push("");
2466
2517
  }
2467
- if (options.byRoute) lines.push(...byRouteTree(p, results, config));
2518
+ if (options.byRoute) lines.push(...byRouteTree(p, results, config, options.verbose ?? false));
2468
2519
  if (summary.dynamic > 0) lines.push(p.dim("\u21AF = set dynamically (verified at runtime)."));
2469
2520
  return lines.join("\n").replace(/\n+$/, "\n");
2470
2521
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",