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 +14 -3
- package/dist/{chunk-LWN65ZOR.js → chunk-L6IHVJKF.js} +112 -22
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
knownRuleIds,
|
|
7
7
|
readPackageVersion,
|
|
8
8
|
run
|
|
9
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "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.
|
|
45
|
+
"@svelte-vitals/core": "0.9.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^24.7.0"
|