@svelte-vitals/vite 0.29.6 → 0.29.7

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.
@@ -130,12 +130,8 @@ function isLoopbackOrigin(origin) {
130
130
  return false;
131
131
  }
132
132
  }
133
- function isLoopbackHost(host) {
134
- return host !== void 0 && isLoopbackOrigin(`http://${host}`);
135
- }
136
133
 
137
134
  export {
138
135
  parseHtmlHead,
139
- isLoopbackOrigin,
140
- isLoopbackHost
136
+ isLoopbackOrigin
141
137
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isLoopbackOrigin,
3
3
  parseHtmlHead
4
- } from "../chunk-2LDMPITM.js";
4
+ } from "../chunk-EHCC47NO.js";
5
5
 
6
6
  // src/hooks/handle.ts
7
7
  import { DEV } from "esm-env";
@@ -10,17 +10,20 @@ import {
10
10
  applyRuleSeverities,
11
11
  defineConfig,
12
12
  effectiveSeverity,
13
+ formatFailedRuleWarning,
13
14
  isPenalized,
14
15
  runRules,
15
- selectRules
16
+ selectRules,
17
+ terminalSafe
16
18
  } from "@svelte-vitals/core";
17
19
  function findingSignature(results, config) {
18
20
  return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
19
21
  }
20
- async function postIngest(origin, route, results) {
22
+ var warn = (line) => console.warn(terminalSafe(line));
23
+ async function postIngest(origin, route, results, failedRuleIds) {
21
24
  if (!isLoopbackOrigin(origin)) {
22
25
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
23
- console.warn(
26
+ warn(
24
27
  `[svelte-vitals] live UI ingest skipped for non-loopback origin ${origin} \u2014 open the dashboard via localhost`
25
28
  );
26
29
  }
@@ -30,7 +33,9 @@ async function postIngest(origin, route, results) {
30
33
  await fetch(`${origin}/__svelte-vitals/ingest`, {
31
34
  method: "POST",
32
35
  headers: { "content-type": "application/json" },
33
- body: JSON.stringify({ route, results })
36
+ // failedRuleIds is always sent, empty array included, so a route that recovers from
37
+ // a previously-crashing rule clears its stale entry on the receiving store.
38
+ body: JSON.stringify({ route, results, failedRuleIds })
34
39
  });
35
40
  } catch {
36
41
  }
@@ -52,16 +57,17 @@ async function analyzeAndIngest(html, route, origin, rules, config, lastSignatur
52
57
  config
53
58
  });
54
59
  const results = applyRuleSeverities(ruleResults, config);
60
+ const failedRuleIds = failedRules.map((f) => f.id);
55
61
  if (failedRules.length > 0 && globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
56
- for (const f of failedRules) console.warn(`[svelte-vitals] rule ${f.id} failed and was skipped: ${f.message}`);
62
+ for (const f of failedRules) warn(formatFailedRuleWarning(f));
57
63
  }
58
- const signature = findingSignature(results, config);
64
+ const signature = `${findingSignature(results, config)}|failed:${[...failedRuleIds].sort().join(",")}`;
59
65
  if (lastSignature.get(route) === signature) return;
60
66
  lastSignature.set(route, signature);
61
- if (globalThis.process?.env?.SVELTE_VITALS_UI) void postIngest(origin, route, results);
67
+ if (globalThis.process?.env?.SVELTE_VITALS_UI) void postIngest(origin, route, results, failedRuleIds);
62
68
  } catch (err) {
63
69
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
64
- console.warn("[svelte-vitals] dev analysis failed:", err);
70
+ warn(`[svelte-vitals] dev analysis failed: ${err instanceof Error ? err.message : String(err)}`);
65
71
  }
66
72
  }
67
73
  }
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import {
2
- isLoopbackHost,
3
2
  isLoopbackOrigin,
4
3
  parseHtmlHead
5
- } from "./chunk-2LDMPITM.js";
4
+ } from "./chunk-EHCC47NO.js";
6
5
 
7
6
  // src/plugin.ts
8
7
  import { existsSync } from "fs";
@@ -13,6 +12,7 @@ import {
13
12
  defaultConfig,
14
13
  resolveRuleOptions,
15
14
  shouldSkipRangeCheck,
15
+ terminalSafe,
16
16
  validateRuleSetting
17
17
  } from "@svelte-vitals/core";
18
18
  import { findUnknownRuleIds, knownRuleIds, ruleOptionsSpec } from "svelte-vitals";
@@ -25,6 +25,7 @@ import {
25
25
  applyOverrides,
26
26
  runRules,
27
27
  withFailedRulesOff,
28
+ formatFailedRuleWarning,
28
29
  computeScore,
29
30
  summarize,
30
31
  hasFailureAtOrAbove,
@@ -232,7 +233,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
232
233
  sourceFiles
233
234
  });
234
235
  const results = applyOverrides(applyRuleSeverities(rawResults, config), config);
235
- for (const f of failedRules) warnings.push(`rule ${f.id} failed and was skipped: ${f.message.split("\n")[0]}`);
236
+ for (const f of failedRules) warnings.push(formatFailedRuleWarning(f));
236
237
  const scoringConfig = withFailedRulesOff(
237
238
  config,
238
239
  failedRules.map((f) => f.id)
@@ -284,7 +285,11 @@ async function resolveMinifyDisabled(minify, configFile, root) {
284
285
  import { CATEGORIES, renderAppShell } from "@svelte-vitals/core";
285
286
 
286
287
  // src/ui/snapshot.ts
287
- import { buildJsonReport, safeHref } from "@svelte-vitals/core";
288
+ import {
289
+ buildJsonReport,
290
+ safeHref,
291
+ withFailedRulesOff as withFailedRulesOff2
292
+ } from "@svelte-vitals/core";
288
293
  function sanitizeDocsUrl(issue) {
289
294
  if (issue.docsUrl === void 0) return issue;
290
295
  if (safeHref(issue.docsUrl) !== null) return issue;
@@ -297,12 +302,14 @@ function sanitizeReport(report) {
297
302
  siteIssues: report.siteIssues.map(sanitizeDocsUrl)
298
303
  };
299
304
  }
300
- function buildSnapshot(store, config, meta) {
305
+ function buildSnapshot(store, config, meta, staticFailedRuleIds) {
306
+ const failedRuleIds = [.../* @__PURE__ */ new Set([...staticFailedRuleIds ?? [], ...store.failedRuleIds()])];
307
+ const scoringConfig = withFailedRulesOff2(config, failedRuleIds);
301
308
  return {
302
309
  // No rule-id list threaded through: `report.rules` is seeded from `store.snapshot()`
303
310
  // alone here, so presence means "produced a result", not "was selected" — unlike the
304
311
  // `json` reporter (design doc 2026-08-03-json-rule-evidence-design.md, Not in scope).
305
- report: sanitizeReport(buildJsonReport(store.snapshot(), config, meta)),
312
+ report: sanitizeReport(buildJsonReport(store.snapshot(), scoringConfig, meta)),
306
313
  badges: store.badges(),
307
314
  analyzing: store.isAnalyzing(),
308
315
  sequence: store.sequence(),
@@ -324,7 +331,7 @@ function isResultLike(x) {
324
331
  const f = r.fix;
325
332
  return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string" && (r.category === void 0 || typeof r.category === "string" && CATEGORY_SET.has(r.category)) && isOptionalString(r.location) && isOptionalString(r.recommendation) && isOptionalString(r.docsUrl) && (f === void 0 || typeof f === "object" && f !== null && typeof f.description === "string" && isOptionalString(f.snippet) && isOptionalString(f.lang)) && (r.line === void 0 || typeof r.line === "number") && isOptionalString(r.route);
326
333
  }
327
- function installUiMiddleware(server, config, version, store, coreVersion) {
334
+ function installUiMiddleware(server, config, version, store, coreVersion, getStaticFailedRuleIds) {
328
335
  const clients = /* @__PURE__ */ new Set();
329
336
  store.subscribe(() => {
330
337
  for (const res of clients) {
@@ -343,7 +350,7 @@ function installUiMiddleware(server, config, version, store, coreVersion) {
343
350
  const url = req.url ?? "/";
344
351
  const origin = req.headers.origin;
345
352
  const host = req.headers.host;
346
- if (typeof origin === "string" && !isLoopbackOrigin(origin) || !isLoopbackHost(host)) {
353
+ if (typeof origin === "string" && !isLoopbackOrigin(origin) || host === void 0 || !isLoopbackOrigin(`http://${host}`)) {
347
354
  req.resume();
348
355
  res.statusCode = 403;
349
356
  res.end("svelte-vitals dev UI is only available from localhost");
@@ -354,9 +361,10 @@ function installUiMiddleware(server, config, version, store, coreVersion) {
354
361
  req.on("data", (c) => chunks.push(c));
355
362
  req.on("end", () => {
356
363
  try {
357
- const { route, results } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
364
+ const { route, results, failedRuleIds } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
358
365
  if (typeof route === "string" && Array.isArray(results)) {
359
- store.set(route, results.filter(isResultLike));
366
+ const failedIds = Array.isArray(failedRuleIds) ? failedRuleIds.filter((id) => typeof id === "string") : [];
367
+ store.set(route, results.filter(isResultLike), failedIds);
360
368
  }
361
369
  } catch {
362
370
  }
@@ -378,7 +386,7 @@ function installUiMiddleware(server, config, version, store, coreVersion) {
378
386
  }
379
387
  if (url.startsWith("/data.json")) {
380
388
  try {
381
- const snapshot = buildSnapshot(store, config, { version, coreVersion });
389
+ const snapshot = buildSnapshot(store, config, { version, coreVersion }, getStaticFailedRuleIds?.());
382
390
  res.setHeader("Content-Type", "application/json");
383
391
  res.end(JSON.stringify(snapshot));
384
392
  } catch {
@@ -388,7 +396,7 @@ function installUiMiddleware(server, config, version, store, coreVersion) {
388
396
  return;
389
397
  }
390
398
  try {
391
- const html = renderAppShell(buildSnapshot(store, config, { version, coreVersion }));
399
+ const html = renderAppShell(buildSnapshot(store, config, { version, coreVersion }, getStaticFailedRuleIds?.()));
392
400
  res.setHeader("Content-Type", "text/html");
393
401
  res.end(html);
394
402
  } catch {
@@ -432,6 +440,7 @@ function composeBadges(staticResults, liveByRoute) {
432
440
  function createStore() {
433
441
  let staticResults = [];
434
442
  const liveByRoute = /* @__PURE__ */ new Map();
443
+ const liveFailedByRoute = /* @__PURE__ */ new Map();
435
444
  const subs = /* @__PURE__ */ new Set();
436
445
  let analyzing = false;
437
446
  let seq = 0;
@@ -440,11 +449,13 @@ function createStore() {
440
449
  for (const fn of subs) fn();
441
450
  }
442
451
  return {
443
- set(route, results) {
452
+ set(route, results, failedRuleIds) {
444
453
  liveByRoute.set(
445
454
  route,
446
455
  results.map((r) => r.route ? r : { ...r, route })
447
456
  );
457
+ if (failedRuleIds && failedRuleIds.length > 0) liveFailedByRoute.set(route, failedRuleIds);
458
+ else liveFailedByRoute.delete(route);
448
459
  notify();
449
460
  },
450
461
  setStatic(results) {
@@ -464,6 +475,9 @@ function createStore() {
464
475
  badges() {
465
476
  return composeBadges(staticResults, liveByRoute);
466
477
  },
478
+ failedRuleIds() {
479
+ return [...new Set([...liveFailedByRoute.values()].flat())].sort();
480
+ },
467
481
  sequence() {
468
482
  return seq;
469
483
  },
@@ -476,28 +490,21 @@ function createStore() {
476
490
 
477
491
  // src/ui/analysis.ts
478
492
  import { relative as relative2, sep } from "path";
493
+ import { analyzeProject } from "svelte-vitals";
479
494
  function createAnalysisRunner(opts) {
480
495
  const debounceMs = opts.debounceMs ?? 500;
481
- let cachedAnalyze = opts.analyze;
496
+ const analyze2 = opts.analyze ?? analyzeProject;
482
497
  const parseCache = /* @__PURE__ */ new Map();
483
498
  let stopped = false;
484
499
  let running = false;
485
500
  let pending = false;
486
501
  let timer;
487
- async function getAnalyze() {
488
- if (cachedAnalyze) return cachedAnalyze;
489
- const mod = await import("svelte-vitals");
490
- const fn = (o) => mod.analyzeProject(o);
491
- cachedAnalyze = fn;
492
- return fn;
493
- }
494
502
  async function runOnce() {
495
503
  if (stopped) return;
496
504
  running = true;
497
505
  opts.onStatusChange?.(true);
498
506
  try {
499
- const analyze2 = await getAnalyze();
500
- const { results } = await analyze2({
507
+ const { results, failedRuleIds } = await analyze2({
501
508
  cwd: opts.root,
502
509
  treatDynamicAs: opts.treatDynamicAs,
503
510
  metaComponents: opts.metaComponents,
@@ -505,7 +512,10 @@ function createAnalysisRunner(opts) {
505
512
  failOn: opts.failOn,
506
513
  parseCache
507
514
  });
508
- if (!stopped) opts.onResults(results);
515
+ if (!stopped) {
516
+ if (failedRuleIds !== void 0) opts.onResults(results, failedRuleIds);
517
+ else opts.onResults(results);
518
+ }
509
519
  } catch (err) {
510
520
  if (!stopped) opts.onError(err);
511
521
  } finally {
@@ -560,6 +570,7 @@ var CONFIG_BASENAMES = /* @__PURE__ */ new Set([
560
570
  "vite.config.cts"
561
571
  ]);
562
572
  var IGNORED_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".svelte-kit", "build", "dist"]);
573
+ var warn = (line) => console.warn(terminalSafe(line));
563
574
  function isRelevant(file, root) {
564
575
  const rel = relative3(root, file);
565
576
  if (rel === "" || rel.startsWith("..")) return false;
@@ -640,10 +651,10 @@ function svelteVitals(options = {}) {
640
651
  resolvedConfig
641
652
  );
642
653
  } catch (err) {
643
- console.warn(`svelte-vitals: skipped \u2014 analysis failed: ${err instanceof Error ? err.message : String(err)}`);
654
+ warn(`svelte-vitals: skipped \u2014 analysis failed: ${err instanceof Error ? err.message : String(err)}`);
644
655
  return;
645
656
  }
646
- for (const w of result.warnings) console.warn(`svelte-vitals: ${w}`);
657
+ for (const w of result.warnings) warn(`svelte-vitals: ${w}`);
647
658
  if (result.routeCount === 0) return;
648
659
  if (options.report !== false) {
649
660
  const out = options.report === "json" ? result.jsonReport : result.consoleReport;
@@ -671,22 +682,26 @@ function svelteVitals(options = {}) {
671
682
  try {
672
683
  ({ config, warnings } = await resolveConfig(uiRoot, options));
673
684
  } catch (err) {
674
- console.warn(
685
+ warn(
675
686
  `svelte-vitals: config file invalid \u2014 dashboard using plugin options/defaults: ${err instanceof Error ? err.message : String(err)}`
676
687
  );
677
688
  config = mergeConfig(options, void 0);
678
689
  warnings = [];
679
690
  }
680
- for (const w of warnings) console.warn(`svelte-vitals: ${w}`);
691
+ for (const w of warnings) warn(`svelte-vitals: ${w}`);
681
692
  const store = createStore();
693
+ let staticFailedRuleIds = [];
682
694
  const runner = createAnalysisRunner({
683
695
  root: uiRoot,
684
696
  treatDynamicAs: options.treatDynamicAs,
685
697
  metaComponents: options.metaComponents,
686
698
  rules: options.rules,
687
699
  failOn: options.failOn,
688
- onResults: (results) => store.setStatic(results),
689
- onError: (err) => console.warn("[svelte-vitals] dev analysis failed:", err),
700
+ onResults: (results, failedRuleIds) => {
701
+ store.setStatic(results);
702
+ staticFailedRuleIds = failedRuleIds ?? [];
703
+ },
704
+ onError: (err) => warn(`[svelte-vitals] dev analysis failed: ${err instanceof Error ? err.message : String(err)}`),
690
705
  onStatusChange: (analyzing) => store.setAnalyzing(analyzing)
691
706
  });
692
707
  runner.start();
@@ -697,7 +712,7 @@ function svelteVitals(options = {}) {
697
712
  delete process.env.SVELTE_VITALS_UI;
698
713
  runner.stop();
699
714
  });
700
- installUiMiddleware(server, config, readPackageVersion(), store, readCoreVersion());
715
+ installUiMiddleware(server, config, readPackageVersion(), store, readCoreVersion(), () => staticFailedRuleIds);
701
716
  if (typeof server.printUrls === "function") {
702
717
  const printUrls = server.printUrls.bind(server);
703
718
  server.printUrls = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.29.6",
3
+ "version": "0.29.7",
4
4
  "description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build, plus a live dev dashboard.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,8 +42,8 @@
42
42
  "esm-env": "^1.2.2",
43
43
  "node-html-parser": "^9.0.1",
44
44
  "tinyglobby": "^0.2.17",
45
- "@svelte-vitals/core": "0.41.1",
46
- "svelte-vitals": "0.45.1"
45
+ "@svelte-vitals/core": "0.42.0",
46
+ "svelte-vitals": "0.46.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@sveltejs/kit": "^2.70.2",