svelte-vitals 0.6.0 → 0.8.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/bin.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  knownRuleIds,
7
7
  readPackageVersion,
8
8
  run
9
- } from "./chunk-LWN65ZOR.js";
9
+ } from "./chunk-L6IHVJKF.js";
10
10
 
11
11
  // src/bin.ts
12
12
  import mri from "mri";
@@ -85,6 +85,7 @@ Options:
85
85
  --json Alias for --reporter=json
86
86
  --fail-on <severity> Fail (exit 1) when any finding reaches this severity: critical | warning | info
87
87
  --fail-on-warning Alias for --fail-on=warning
88
+ --min-health <0-100> Fail (exit 1) when the combined Health score is below this value
88
89
  --rules <ids> Comma-separated rule ids to enable (all others disabled)
89
90
  --ignore <ids> Comma-separated rule ids to disable
90
91
  -h, --help Show this help
@@ -99,7 +100,7 @@ async function main() {
99
100
  const argv = mri(process.argv.slice(2), {
100
101
  alias: { h: "help", v: "version" },
101
102
  boolean: ["by-route", "json", "fail-on-warning"],
102
- string: ["meta-components", "treat-dynamic-as", "route", "fail-on", "reporter", "rules", "ignore"]
103
+ string: ["meta-components", "treat-dynamic-as", "route", "fail-on", "reporter", "rules", "ignore", "min-health"]
103
104
  });
104
105
  if (argv.help) {
105
106
  console.log(HELP);
@@ -113,7 +114,17 @@ async function main() {
113
114
  for (const w of warnings) console.error(w);
114
115
  for (const e of errors) console.error(e);
115
116
  if (!options) process.exit(2);
116
- const code = await run(options);
117
+ const minHealthRaw = argv["min-health"];
118
+ let minHealth;
119
+ if (minHealthRaw !== void 0) {
120
+ const n = Number(minHealthRaw);
121
+ if (!Number.isFinite(n) || n < 0 || n > 100) {
122
+ console.error(`svelte-vitals: invalid --min-health '${minHealthRaw}'; expected a number 0-100.`);
123
+ process.exit(2);
124
+ }
125
+ minHealth = n;
126
+ }
127
+ const code = await run({ ...options, minHealth });
117
128
  process.exit(code);
118
129
  }
119
130
  void main();
@@ -9,6 +9,7 @@ import {
9
9
  formatGithubReport,
10
10
  summarize,
11
11
  hasFailureAtOrAbove,
12
+ computeHealth,
12
13
  defineConfig,
13
14
  selectRules,
14
15
  applyRuleSeverities
@@ -471,6 +472,105 @@ async function collectRoutes(rt, cwd, config = defaultConfig) {
471
472
  };
472
473
  }
473
474
 
475
+ // src/providers/source/a11y.ts
476
+ import { compile } from "svelte/compiler";
477
+ import { defaultConfig as defaultConfig2 } from "@svelte-vitals/core";
478
+
479
+ // src/rules-config.ts
480
+ import { allRules } from "@svelte-vitals/core";
481
+ var KNOWN_IDS = new Set(allRules.map((r) => r.id));
482
+ var A11Y_CODE_PREFIX = "a11y_";
483
+ var A11Y_CATEGORY_KEY = "a11y_category";
484
+ function findUnknownRuleIds(ids) {
485
+ return [
486
+ ...new Set(ids.filter((id) => !KNOWN_IDS.has(id) && !(id.startsWith(A11Y_CODE_PREFIX) && id !== A11Y_CATEGORY_KEY)))
487
+ ];
488
+ }
489
+ function knownRuleIds() {
490
+ return [...KNOWN_IDS].sort();
491
+ }
492
+ function buildRulesConfig(allow, ignore) {
493
+ const rules = {};
494
+ if (allow.length > 0) {
495
+ for (const r of allRules) if (!allow.includes(r.id)) rules[r.id] = "off";
496
+ if (!allow.some((id) => id.startsWith(A11Y_CODE_PREFIX))) rules[A11Y_CATEGORY_KEY] = "off";
497
+ }
498
+ for (const id of ignore) rules[id] = "off";
499
+ return rules;
500
+ }
501
+
502
+ // src/providers/source/a11y.ts
503
+ function firstLine(message) {
504
+ return message.split("\n")[0] ?? message;
505
+ }
506
+ function fileA11y(source, rel) {
507
+ let warnings;
508
+ try {
509
+ ({ warnings } = compile(source, { generate: false, filename: rel }));
510
+ } catch {
511
+ return { ok: false, warnings: [] };
512
+ }
513
+ const out = [];
514
+ for (const w of warnings) {
515
+ if (w.code.startsWith(A11Y_CODE_PREFIX)) {
516
+ out.push({
517
+ code: w.code,
518
+ message: firstLine(w.message),
519
+ line: w.start?.line ?? 0
520
+ });
521
+ }
522
+ }
523
+ return { ok: true, warnings: out };
524
+ }
525
+ async function collectA11y(rt, cwd, config = defaultConfig2) {
526
+ if (config.rules[A11Y_CATEGORY_KEY] === "off") return [];
527
+ const pages = await enumerateRoutePages(rt, cwd);
528
+ const cache = /* @__PURE__ */ new Map();
529
+ const results = [];
530
+ for (const page of pages) {
531
+ const files = await chainFiles(rt, cwd, page);
532
+ const route = deriveRoute(page);
533
+ const fails = [];
534
+ let compiledAll = true;
535
+ for (const { rel } of files) {
536
+ let entry = cache.get(rel);
537
+ if (!entry) {
538
+ const source = await rt.readFile(rt.join(cwd, rel));
539
+ entry = fileA11y(source, rel);
540
+ cache.set(rel, entry);
541
+ }
542
+ if (!entry.ok) compiledAll = false;
543
+ for (const w of entry.warnings) {
544
+ if (config.rules[w.code] === "off") continue;
545
+ fails.push({
546
+ id: w.code,
547
+ category: "a11y",
548
+ severity: "warning",
549
+ detection: { presence: "none", value: "absent" },
550
+ route,
551
+ location: rel,
552
+ ...w.line > 0 ? { line: w.line } : {},
553
+ message: w.message,
554
+ docsUrl: `https://svelte.dev/e/${w.code}`
555
+ });
556
+ }
557
+ }
558
+ if (fails.length > 0) {
559
+ results.push(...fails);
560
+ } else if (compiledAll) {
561
+ results.push({
562
+ id: "a11y",
563
+ category: "a11y",
564
+ severity: "warning",
565
+ detection: { presence: "own", value: "static" },
566
+ route,
567
+ message: "Accessibility"
568
+ });
569
+ }
570
+ }
571
+ return results;
572
+ }
573
+
474
574
  // src/version.ts
475
575
  import { readFileSync } from "fs";
476
576
  function readPackageVersion() {
@@ -511,24 +611,6 @@ function isAutoDetectedGithub(explicit, env = process.env) {
511
611
  return !explicit && !isReporterName(env.SVELTE_VITALS_REPORTER) && !isAgentEnv(env) && isGithubActionsEnv(env);
512
612
  }
513
613
 
514
- // src/rules-config.ts
515
- import { allRules } from "@svelte-vitals/core";
516
- var KNOWN_IDS = new Set(allRules.map((r) => r.id));
517
- function findUnknownRuleIds(ids) {
518
- return [...new Set(ids.filter((id) => !KNOWN_IDS.has(id)))];
519
- }
520
- function knownRuleIds() {
521
- return [...KNOWN_IDS].sort();
522
- }
523
- function buildRulesConfig(allow, ignore) {
524
- const rules = {};
525
- if (allow.length > 0) {
526
- for (const r of allRules) if (!allow.includes(r.id)) rules[r.id] = "off";
527
- }
528
- for (const id of ignore) rules[id] = "off";
529
- return rules;
530
- }
531
-
532
614
  // src/index.ts
533
615
  function routeMatcher(glob) {
534
616
  if (!glob) return () => true;
@@ -552,12 +634,18 @@ async function analyzeProject(opts = {}) {
552
634
  const images = collected.images.filter((i) => matches(i.route));
553
635
  const project = await collectProjectFacts(rt, cwd);
554
636
  const rules = selectRules(allRules2, config);
555
- const results = applyRuleSeverities(await runRules(rules, { heads, images, project, config }), config);
637
+ const a11y = (await collectA11y(rt, cwd, config)).filter((r) => r.route === void 0 || matches(r.route));
638
+ const ruleResults = await runRules(rules, { heads, images, project, config });
639
+ const results = applyRuleSeverities([...ruleResults, ...a11y], config);
556
640
  return { results, config, version: readPackageVersion() };
557
641
  }
558
642
  async function run(opts = {}) {
559
643
  const log = opts.log ?? ((line) => console.log(line));
560
644
  const errorLog = opts.errorLog ?? ((line) => console.error(line));
645
+ if (opts.minHealth != null && (!Number.isFinite(opts.minHealth) || opts.minHealth < 0 || opts.minHealth > 100)) {
646
+ errorLog(`svelte-vitals: invalid minHealth '${opts.minHealth}'; expected a number 0-100.`);
647
+ return 2;
648
+ }
561
649
  let analysis;
562
650
  try {
563
651
  analysis = await analyzeProject({
@@ -603,7 +691,9 @@ async function run(opts = {}) {
603
691
  log(formatConsoleReport(results, config, { byRoute: opts.byRoute ?? false }));
604
692
  }
605
693
  const summary = summarize(results, config);
606
- return hasFailureAtOrAbove(summary, config.failOn) ? 1 : 0;
694
+ const failBySeverity = hasFailureAtOrAbove(summary, config.failOn);
695
+ const failByHealth = opts.minHealth != null && computeHealth(results, config).health < opts.minHealth;
696
+ return failBySeverity || failByHealth ? 1 : 0;
607
697
  } catch (err) {
608
698
  errorLog(`svelte-vitals: ${err instanceof Error ? err.message : String(err)}`);
609
699
  return 2;
@@ -612,11 +702,11 @@ async function run(opts = {}) {
612
702
 
613
703
  export {
614
704
  ProjectError,
615
- readPackageVersion,
616
- isReporterName,
617
705
  findUnknownRuleIds,
618
706
  knownRuleIds,
619
707
  buildRulesConfig,
708
+ readPackageVersion,
709
+ isReporterName,
620
710
  routeMatcher,
621
711
  analyzeProject,
622
712
  run
package/dist/index.d.ts CHANGED
@@ -16,6 +16,10 @@ declare function knownRuleIds(): string[];
16
16
  * (--ignore). An allow-list disables every rule not listed; deny always wins.
17
17
  * Callers should reject unknown ids first (see findUnknownRuleIds) so a typo in
18
18
  * --rules can't silently disable every rule.
19
+ *
20
+ * When the allow-list is non-empty and contains no `a11y_*` entries, the
21
+ * `A11Y_CATEGORY_KEY` sentinel is set to `'off'`; `collectA11y` checks this key
22
+ * to suppress the entire Accessibility category.
19
23
  */
20
24
  declare function buildRulesConfig(allow: string[], ignore: string[]): Record<string, RuleSetting>;
21
25
 
@@ -33,6 +37,8 @@ interface RunOptions {
33
37
  rules?: Record<string, RuleSetting>;
34
38
  /** Override process.env for reporter auto-detection (mainly useful in tests). */
35
39
  env?: NodeJS.ProcessEnv;
40
+ /** Fail (exit 1) when the combined Health score is below this value (0–100). */
41
+ minHealth?: number;
36
42
  }
37
43
  declare function routeMatcher(glob: string | undefined): (route: string) => boolean;
38
44
  interface AnalyzeOptions {
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  knownRuleIds,
7
7
  routeMatcher,
8
8
  run
9
- } from "./chunk-LWN65ZOR.js";
9
+ } from "./chunk-L6IHVJKF.js";
10
10
  export {
11
11
  ProjectError,
12
12
  analyzeProject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-vitals",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "A SvelteKit SEO checker — not a runtime Web Vitals reporter. Static analysis of your routes' head metadata.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  "mri": "^1.2.0",
43
43
  "svelte": "^5.56.3",
44
44
  "tinyglobby": "^0.2.17",
45
- "@svelte-vitals/core": "0.8.0"
45
+ "@svelte-vitals/core": "0.9.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^24.7.0"