svelte-vitals 0.54.6 → 0.55.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/{analyze-B1DhJ_wC.js → analyze-HCxZ89Gm.js} +1 -1
- package/dist/bin.js +6 -6
- package/dist/{ci-Cs-AbY0g.js → ci-BsCX5NZs.js} +1 -1
- package/dist/{complete-B5MjMWIx.js → complete-CU5WZYZT.js} +6 -6
- package/dist/{docs-CS6y-jmw.js → docs-C9bKlGnX.js} +3 -3
- package/dist/{explain-2q_Uvl0L.js → explain-DxY_9zU0.js} +2 -2
- package/dist/gunshi-registry.js +2 -2
- package/dist/index.d.ts +45 -1
- package/dist/index.js +2 -2
- package/dist/{install-NaV82t9A.js → install-71cr0Vpl.js} +2 -2
- package/dist/{src-CNeKZkf0.js → src-BUwLRzL0.js} +74 -17
- package/package.json +10 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as findUnknownRuleIds,
|
|
1
|
+
import { _ as readCoreVersion, c as findUnknownRuleIds, g as isReporterName, i as run, l as knownRuleIds, v as readPackageVersion } from "./src-BUwLRzL0.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { terminalSafe } from "@svelte-vitals/core/internal";
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { g as realIO, i as resolveLocale, m as consoleIO, n as runAnalyzeCliGunshi } from "./analyze-
|
|
2
|
+
import { g as realIO, i as resolveLocale, m as consoleIO, n as runAnalyzeCliGunshi } from "./analyze-HCxZ89Gm.js";
|
|
3
3
|
import { terminalSafe } from "@svelte-vitals/core/internal";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
/**
|
|
@@ -17,35 +17,35 @@ async function runCli(argv, io = consoleIO, env = process.env) {
|
|
|
17
17
|
try {
|
|
18
18
|
const locale = resolveLocale(env);
|
|
19
19
|
if (argv[0] === "complete") {
|
|
20
|
-
const { runCompleteCliGunshi } = await import("./complete-
|
|
20
|
+
const { runCompleteCliGunshi } = await import("./complete-CU5WZYZT.js");
|
|
21
21
|
return {
|
|
22
22
|
code: await runCompleteCliGunshi(argv, io),
|
|
23
23
|
exit: "natural"
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
if (argv[0] === "docs") {
|
|
27
|
-
const { runDocsCliGunshi } = await import("./docs-
|
|
27
|
+
const { runDocsCliGunshi } = await import("./docs-C9bKlGnX.js");
|
|
28
28
|
return {
|
|
29
29
|
code: await runDocsCliGunshi(argv.slice(1), io, locale),
|
|
30
30
|
exit: "natural"
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
if (argv[0] === "explain") {
|
|
34
|
-
const { runExplainCliGunshi } = await import("./explain-
|
|
34
|
+
const { runExplainCliGunshi } = await import("./explain-DxY_9zU0.js");
|
|
35
35
|
return {
|
|
36
36
|
code: await runExplainCliGunshi(argv.slice(1), io, locale),
|
|
37
37
|
exit: "natural"
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
if (argv[0] === "install") {
|
|
41
|
-
const { runInstallCliGunshi } = await import("./install-
|
|
41
|
+
const { runInstallCliGunshi } = await import("./install-71cr0Vpl.js").then((n) => n.n);
|
|
42
42
|
return {
|
|
43
43
|
code: await runInstallCliGunshi(argv.slice(1), io, locale),
|
|
44
44
|
exit: "immediate"
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
if (argv[0] === "ci") {
|
|
48
|
-
const { runCiCliGunshi } = await import("./ci-
|
|
48
|
+
const { runCiCliGunshi } = await import("./ci-BsCX5NZs.js");
|
|
49
49
|
return {
|
|
50
50
|
code: await runCiCliGunshi(argv.slice(1), {
|
|
51
51
|
...realIO(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as guardArgs, c as stripUnknownFlags, g as realIO, l as suggestClosest, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
1
|
+
import { a as guardArgs, c as stripUnknownFlags, g as realIO, l as suggestClosest, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-HCxZ89Gm.js";
|
|
2
2
|
import { a as planWorkflowWrite, i as buildWorkflowYaml, n as ACTION_VERSION, r as WORKFLOW_PATH, t as ACTION_SHA } from "./action-pin.generated-BkwgIAMh.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { cli } from "gunshi/bone";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { d as FAIL_ON_VALUES, f as TREAT_DYNAMIC_AS_VALUES, m as consoleIO, t as ROOT_ARGS, u as CATEGORIES } from "./analyze-
|
|
3
|
-
import { t as INSTALL_ARGS } from "./install-
|
|
4
|
-
import { CI_ARGS, CI_UPGRADE_ARGS } from "./ci-
|
|
5
|
-
import { DOCS_LIST_ARGS, DOCS_ROOT_ARGS, DOCS_SHOW_ARGS } from "./docs-
|
|
6
|
-
import { EXPLAIN_ARGS } from "./explain-
|
|
1
|
+
import { h as REPORTER_NAMES } from "./src-BUwLRzL0.js";
|
|
2
|
+
import { d as FAIL_ON_VALUES, f as TREAT_DYNAMIC_AS_VALUES, m as consoleIO, t as ROOT_ARGS, u as CATEGORIES } from "./analyze-HCxZ89Gm.js";
|
|
3
|
+
import { t as INSTALL_ARGS } from "./install-71cr0Vpl.js";
|
|
4
|
+
import { CI_ARGS, CI_UPGRADE_ARGS } from "./ci-BsCX5NZs.js";
|
|
5
|
+
import { DOCS_LIST_ARGS, DOCS_ROOT_ARGS, DOCS_SHOW_ARGS } from "./docs-C9bKlGnX.js";
|
|
6
|
+
import { EXPLAIN_ARGS } from "./explain-DxY_9zU0.js";
|
|
7
7
|
import { cli } from "gunshi/bone";
|
|
8
8
|
import { define } from "gunshi/definition";
|
|
9
9
|
import { kebabnize } from "gunshi/utils";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { l as knownRuleIds } from "./src-
|
|
2
|
-
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
1
|
+
import { l as knownRuleIds } from "./src-BUwLRzL0.js";
|
|
2
|
+
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-HCxZ89Gm.js";
|
|
3
3
|
import { docsUrlFor } from "@svelte-vitals/core/internal";
|
|
4
4
|
import { cli } from "gunshi/bone";
|
|
5
5
|
import { define } from "gunshi/definition";
|
|
@@ -34,7 +34,7 @@ const EMBEDDED_DOCS = [
|
|
|
34
34
|
name: "scoping",
|
|
35
35
|
title: "Scoping findings to a change",
|
|
36
36
|
description: "Use --diff, --staged, --baseline and the suppressions file so only what a change introduced is reported, instead of a legacy backlog.",
|
|
37
|
-
body: "# Scoping findings to a change\n\nAn existing project usually has a backlog nobody is about to fix. Scope the report rather than\ndisabling rules.\n\n## Scope by file\n\n- `--diff [ref]`: only findings in files changed versus `ref` (default `HEAD`).\n- `--staged`: only findings in staged files. The pre-commit gate.\n\n```bash\nsvelte-vitals . --diff --reporter agent # after editing: what did I just break?\nsvelte-vitals . --staged # before committing\n```\n\nBoth work when the project is not at the git repo root.\n\n## Scope by finding (`--baseline <ref>`)\n\nReports only findings **not already present** at `ref`. Scopes by finding identity rather than by\nfile, so a pre-existing problem in a file you touched does not fail the gate. No default ref.\n\n```bash\nsvelte-vitals --diff origin/main --baseline origin/main --fail-on warning # PR gate\n```\n\nIt checks `ref` out into a temporary worktree and subtracts those findings. On failure (no git,\nbad ref) it warns and reports everything rather than failing the run.\n\n## Accept a backlog once (`svelte-vitals-suppressions.json`)\n\n`--baseline` handles the transient case. For a persistent ramp, record today's findings once:\n\n```bash\nsvelte-vitals --update-suppressions\ngit add svelte-vitals-suppressions.json\n```\n\nThis analyzes the whole project (`--diff`/`--staged`/`--baseline` are ignored), writes every\npenalized finding, and exits `0` without a report.\n\nThe file then applies automatically on every run, after `--diff`/`--staged` and `--baseline
|
|
37
|
+
body: "# Scoping findings to a change\n\nAn existing project usually has a backlog nobody is about to fix. Scope the report rather than\ndisabling rules.\n\n## Scope by file\n\n- `--diff [ref]`: only findings in files changed versus `ref` (default `HEAD`).\n- `--staged`: only findings in staged files. The pre-commit gate.\n\n```bash\nsvelte-vitals . --diff --reporter agent # after editing: what did I just break?\nsvelte-vitals . --staged # before committing\n```\n\nBoth work when the project is not at the git repo root.\n\n## Scope by finding (`--baseline <ref>`)\n\nReports only findings **not already present** at `ref`. Scopes by finding identity rather than by\nfile, so a pre-existing problem in a file you touched does not fail the gate. No default ref.\n\n```bash\nsvelte-vitals --diff origin/main --baseline origin/main --fail-on warning # PR gate\n```\n\nIt checks `ref` out into a temporary worktree and subtracts those findings. On failure (no git,\nbad ref) it warns and reports everything rather than failing the run.\n\n## Accept a backlog once (`svelte-vitals-suppressions.json`)\n\n`--baseline` handles the transient case. For a persistent ramp, record today's findings once:\n\n```bash\nsvelte-vitals --update-suppressions\ngit add svelte-vitals-suppressions.json\n```\n\nThis analyzes the whole project (`--diff`/`--staged`/`--baseline` are ignored), writes every\npenalized finding, and exits `0` without a report.\n\nThe file then applies automatically on every run, after `--diff`/`--staged` and `--baseline` —\nin `vite build` and the live dashboard too (source-scan entries only; route-level entries are\nskipped there, since rendered route findings anchor to the built HTML).\nFixing an accepted finding leaves a **stale** entry, reported on stderr but never failing the run.\n`--no-suppressions` ignores the file for one run.\n\nAn entry covers whatever its rule reports at that route and location, not just the message\nrecorded when written, so a different finding from the same rule at the same spot still matches\nand stays suppressed (and not stale).\n\nA malformed suppressions file is a hard error (exit `2`), not a silent skip.\n\n## Suppress one occurrence inline\n\nFor a single finding that is correct by design, put a `svelte-vitals-disable-next-line` comment on\nthe line directly above it:\n\n```html\n<!-- svelte-vitals-disable-next-line security/raw-html -->\n<div>{@html sanitized}</div>\n```\n\nInside `<script>`, use `// svelte-vitals-disable-next-line <rule-id>`. Omit the id to suppress every\nrule on the next line, or list several comma-separated.\n\nOnly findings the report anchors to a **line** can be reached this way, not the `<head>` metadata\nrules, which report what a route never set. A directive in a component silences that finding on\nevery route composing it; for one route, use the suppressions file or `overrides`.\n\n## Which one\n\n| Situation | Use |\n| -------------------------------------- | ---------------------------------------- |\n| Checking an edit you just made | `--diff` |\n| One occurrence that is right as-is | `svelte-vitals-disable-next-line` |\n| Pre-commit hook | `--staged` |\n| PR gate against a base branch | `--diff <base> --baseline <base>` |\n| Adopting on a legacy project, for good | `--update-suppressions`, commit the file |\n\nMatching ignores line numbers in both `--baseline` and the suppressions file, so a second\nviolation of the same rule lower in the same file does not surface as new.\n\n## Related\n\n- `svelte-vitals docs show ci`: the generated PR gate already does the `--diff`/`--baseline` pairing\n- `svelte-vitals docs show config`: turning a rule off for good, when that is genuinely right"
|
|
38
38
|
}
|
|
39
39
|
];
|
|
40
40
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { l as knownRuleIds } from "./src-
|
|
2
|
-
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
1
|
+
import { l as knownRuleIds } from "./src-BUwLRzL0.js";
|
|
2
|
+
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-HCxZ89Gm.js";
|
|
3
3
|
import { CATEGORIES } from "@svelte-vitals/core";
|
|
4
4
|
import { allRules, explainRule } from "@svelte-vitals/core/internal";
|
|
5
5
|
import { cli } from "gunshi/bone";
|
package/dist/gunshi-registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ROOT_ARGS } from "./analyze-
|
|
2
|
-
import { t as INSTALL_ARGS } from "./install-
|
|
1
|
+
import { t as ROOT_ARGS } from "./analyze-HCxZ89Gm.js";
|
|
2
|
+
import { t as INSTALL_ARGS } from "./install-71cr0Vpl.js";
|
|
3
3
|
import { t as JA_ARG_DESCRIPTIONS } from "./ja-BDLP3KLG.js";
|
|
4
4
|
export { INSTALL_ARGS, JA_ARG_DESCRIPTIONS, ROOT_ARGS };
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ interface ParsedFile {
|
|
|
71
71
|
imports: ImportMap;
|
|
72
72
|
images: ParsedImage[];
|
|
73
73
|
headings: ParsedHeading[];
|
|
74
|
+
/** This file has a `<svelte:element>` that may render a heading of an undetermined level. */
|
|
75
|
+
dynamicHeading: boolean;
|
|
74
76
|
a11y: ParsedA11y;
|
|
75
77
|
/** Inline `svelte-vitals-disable-next-line` directives in this file, for the central
|
|
76
78
|
* suppression pass. Collected here because a route-scoped finding can be located in any file
|
|
@@ -159,6 +161,46 @@ export declare function ruleOptionsSpec(id: string): RuleOptionsSpec | undefined
|
|
|
159
161
|
*/
|
|
160
162
|
export declare function registryTag(): string;
|
|
161
163
|
//#endregion
|
|
164
|
+
//#region src/suppressions.d.ts
|
|
165
|
+
/**
|
|
166
|
+
* Persistent adoption ramp (design doc 2026-07-13-suppressions-file-design.md):
|
|
167
|
+
* accept every currently-penalized finding once (`--update-suppressions`), then
|
|
168
|
+
* gate only on findings that appear afterward. Unlike `--baseline <ref>` (a
|
|
169
|
+
* transient git-ref comparison), this file is committed and applied on every run.
|
|
170
|
+
*/
|
|
171
|
+
export declare const SUPPRESSIONS_FILE = "svelte-vitals-suppressions.json";
|
|
172
|
+
interface SuppressionEntry {
|
|
173
|
+
id: string;
|
|
174
|
+
route?: string;
|
|
175
|
+
location?: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Reads `svelte-vitals-suppressions.json` from `cwd`. Returns `undefined` when
|
|
179
|
+
* the file doesn't exist. Throws when the file exists but is not valid JSON, its
|
|
180
|
+
* top-level shape isn't `{ version: 1, suppressions: [...] }`, or an entry is
|
|
181
|
+
* missing a string `id` — a silently-ignored typo would un-gate CI, so malformed
|
|
182
|
+
* files are a hard error (mapped to exit 2 by the caller). Unknown keys (both
|
|
183
|
+
* top-level and per-entry) are ignored for forward compatibility.
|
|
184
|
+
*/
|
|
185
|
+
export declare function loadSuppressions(cwd: string): SuppressionEntry[] | undefined;
|
|
186
|
+
/**
|
|
187
|
+
* Removes penalized findings whose key matches a suppression entry. Passing
|
|
188
|
+
* seeds are never removed even if their key happens to match. Reports how many
|
|
189
|
+
* findings were suppressed and how many entries in `entries` matched nothing
|
|
190
|
+
* (stale — most likely fixed since the file was last written).
|
|
191
|
+
*
|
|
192
|
+
* `allResults` (defaults to `results`) is used only for the staleness tally, not
|
|
193
|
+
* for filtering: a caller that narrows `results` to a subset of files/findings
|
|
194
|
+
* (`--diff`/`--staged`/`--baseline`) can still pass the pre-scope, project-wide
|
|
195
|
+
* result set here so an entry whose finding survives elsewhere in the project
|
|
196
|
+
* isn't misreported as stale just because this run's scope excluded it.
|
|
197
|
+
*/
|
|
198
|
+
export declare function applySuppressions(results: Result[], entries: SuppressionEntry[], config: Config, allResults?: Result[]): {
|
|
199
|
+
results: Result[];
|
|
200
|
+
suppressed: number;
|
|
201
|
+
stale: number;
|
|
202
|
+
};
|
|
203
|
+
//#endregion
|
|
162
204
|
//#region src/index.d.ts
|
|
163
205
|
export interface RunOptions {
|
|
164
206
|
cwd?: string;
|
|
@@ -277,6 +319,8 @@ export interface AnalyzeOptions {
|
|
|
277
319
|
allowRules?: string[];
|
|
278
320
|
/** Per-category weights for the combined Health score (flag > config file > default 1 each). */
|
|
279
321
|
weights?: Partial<Record<Category, number>>;
|
|
322
|
+
/** SEO-wide switches (config-file field; no CLI flag). `indexable: false` turns off the search-result rules. */
|
|
323
|
+
seo?: Config['seo'];
|
|
280
324
|
/** Restrict analysis to rules in these categories (applied after rules/ignore selection). */
|
|
281
325
|
categories?: Category[];
|
|
282
326
|
/**
|
|
@@ -378,4 +422,4 @@ export declare function applyScope(results: Result[], opts: ApplyScopeOptions):
|
|
|
378
422
|
*/
|
|
379
423
|
export declare function run(opts?: RunOptions): Promise<number>;
|
|
380
424
|
//#endregion
|
|
381
|
-
export { type LoadedConfigFile, type ParseCache, defineConfig };
|
|
425
|
+
export { type LoadedConfigFile, type ParseCache, type SuppressionEntry, defineConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CONFIG_FILENAMES, ProjectError, analyzeProject, applyScope, createNodeRuntime, defineConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
|
|
1
|
+
import { C as ProjectError, S as routeMatcher, a as spinnerEnabled, c as findUnknownRuleIds, d as ruleOptionsSpec, f as SUPPRESSIONS_FILE, i as run, l as knownRuleIds, m as loadSuppressions, n as applyScope, o as CONFIG_FILENAMES, p as applySuppressions, r as defineConfig, s as loadConfigFile, t as analyzeProject, u as registryTag, w as createNodeRuntime } from "./src-BUwLRzL0.js";
|
|
2
|
+
export { CONFIG_FILENAMES, ProjectError, SUPPRESSIONS_FILE, analyzeProject, applyScope, applySuppressions, createNodeRuntime, defineConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, loadSuppressions, registryTag, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import { a as guardArgs, c as stripUnknownFlags, g as realIO, h as clackPrompts, m as consoleIO, o as splitAtTerminator, p as toList, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
2
|
+
import { b as hasDep, o as CONFIG_FILENAMES, v as readPackageVersion, x as readPkg, y as discoverApps } from "./src-BUwLRzL0.js";
|
|
3
|
+
import { a as guardArgs, c as stripUnknownFlags, g as realIO, h as clackPrompts, m as consoleIO, o as splitAtTerminator, p as toList, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-HCxZ89Gm.js";
|
|
4
4
|
import { a as planWorkflowWrite, i as buildWorkflowYaml, n as ACTION_VERSION, r as WORKFLOW_PATH, t as ACTION_SHA } from "./action-pin.generated-BkwgIAMh.js";
|
|
5
5
|
import { buildCursorRules, installHeader } from "./install/skill-content.js";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { CATEGORIES, defineConfig, defineConfig as defineConfig$1 } from "@svelte-vitals/core";
|
|
4
|
-
import { ANCESTRY_DEPENDENT_TAGS, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, addFactsDirectives, allRules, attrText, attrTextOf, attrValue, attrValueOf, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, decodeFragmentId, defaultConfig, docsUrlFor, findAttr, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, isClassicScriptType, isPenalized, isPlainObject, isTopFragment, lineOf, noColorPalette, parseSvelte, resolveKitAliases, resolveKitPathsBase, resolveLandmark, resolveRepoLocalPath, resolveRuleOptions, runAnalysis, scoreColor, selectRules, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleSetting, valueFromNodes, withReadLimit } from "@svelte-vitals/core/internal";
|
|
4
|
+
import { ANCESTRY_DEPENDENT_TAGS, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, addFactsDirectives, allRules, attrText, attrTextOf, attrValue, attrValueOf, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, decodeFragmentId, defaultConfig, docsUrlFor, findAttr, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, isClassicScriptType, isPenalized, isPlainObject, isTopFragment, lineOf, noColorPalette, parseSvelte, resolveKitAliases, resolveKitPathsBase, resolveLandmark, resolveRepoLocalPath, resolveRuleOptions, runAnalysis, scoreColor, selectRules, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleSetting, valueFromNodes, withIndexableOff, withReadLimit } from "@svelte-vitals/core/internal";
|
|
5
5
|
import { access, glob, readFile, stat } from "node:fs/promises";
|
|
6
6
|
import { getAgentProfile } from "gunshi/agent";
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
@@ -714,7 +714,30 @@ function collectImages(node, source, acc) {
|
|
|
714
714
|
}
|
|
715
715
|
for (const key of CHILD_NODE_KEYS) if (key in node) collectImages(childOf(node, key), source, acc);
|
|
716
716
|
}
|
|
717
|
-
/**
|
|
717
|
+
/**
|
|
718
|
+
* The tag names a `<svelte:element this={…}>` can render as, or undefined when the expression
|
|
719
|
+
* is not statically determinable. A string literal gives one name; a conditional whose branches
|
|
720
|
+
* are both literals gives both, so the common `this={cond ? 'h1' : 'span'}` reads as the set it
|
|
721
|
+
* really is rather than as an unknown.
|
|
722
|
+
*/
|
|
723
|
+
function svelteElementTags(tag) {
|
|
724
|
+
if (tag.type === "Literal") return typeof tag.value === "string" ? [tag.value] : void 0;
|
|
725
|
+
if (tag.type === "ConditionalExpression") {
|
|
726
|
+
const branches = [tag.consequent, tag.alternate].map((b) => b.type === "Literal" && typeof b.value === "string" ? b.value : void 0);
|
|
727
|
+
return branches.every((b) => b !== void 0) ? [...new Set(branches)] : void 0;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const HEADING_TAG = /^h[1-6]$/;
|
|
731
|
+
/** The one heading level a resolved tag set renders as, or undefined (no heading, or two levels). */
|
|
732
|
+
function headingLevelOf(tags) {
|
|
733
|
+
const levels = new Set(tags.map((t) => t.toLowerCase()).filter((t) => HEADING_TAG.test(t)).map((t) => Number(t[1])));
|
|
734
|
+
return levels.size === 1 ? [...levels][0] : void 0;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Recursively collect page-body headings (<h1>–<h6>) anywhere in the template (seo/single-h1).
|
|
738
|
+
* `acc.dynamic` records a `<svelte:element>` that may render a heading but whose level is not
|
|
739
|
+
* statically determinable — a route carrying one cannot be reported as having no <h1>.
|
|
740
|
+
*/
|
|
718
741
|
function collectHeadings(node, source, acc) {
|
|
719
742
|
if (Array.isArray(node)) {
|
|
720
743
|
for (const child of node) collectHeadings(child, source, acc);
|
|
@@ -722,10 +745,19 @@ function collectHeadings(node, source, acc) {
|
|
|
722
745
|
}
|
|
723
746
|
if (!node || typeof node !== "object") return;
|
|
724
747
|
if (node.type === "SvelteHead") return;
|
|
725
|
-
if (node.type === "RegularElement" &&
|
|
748
|
+
if (node.type === "RegularElement" && HEADING_TAG.test(node.name)) acc.headings.push({
|
|
726
749
|
level: Number(node.name[1]),
|
|
727
750
|
line: lineOf(source, node.start)
|
|
728
751
|
});
|
|
752
|
+
else if (node.type === "SvelteElement") {
|
|
753
|
+
const tags = svelteElementTags(node.tag);
|
|
754
|
+
const level = tags ? headingLevelOf(tags) : void 0;
|
|
755
|
+
if (level !== void 0) acc.headings.push({
|
|
756
|
+
level,
|
|
757
|
+
line: lineOf(source, node.start)
|
|
758
|
+
});
|
|
759
|
+
else if (!tags || tags.some((t) => HEADING_TAG.test(t.toLowerCase()))) acc.dynamic = true;
|
|
760
|
+
}
|
|
729
761
|
for (const key of CHILD_NODE_KEYS) if (key in node) collectHeadings(childOf(node, key), source, acc);
|
|
730
762
|
}
|
|
731
763
|
/** An `aria-label`/`aria-labelledby` carrying a name: a non-blank literal, or an expression whose
|
|
@@ -826,11 +858,13 @@ function collectA11y(fragment, source) {
|
|
|
826
858
|
});
|
|
827
859
|
return;
|
|
828
860
|
case "RegularElement":
|
|
829
|
-
case "SvelteElement":
|
|
830
|
-
|
|
831
|
-
|
|
861
|
+
case "SvelteElement": {
|
|
862
|
+
const tags = node.type === "SvelteElement" ? svelteElementTags(node.tag) : [node.name];
|
|
863
|
+
if (!tags) elementsUnknowable = true;
|
|
864
|
+
else for (const tag of tags) elementTags.add(tag.toLowerCase());
|
|
832
865
|
walkElement(node, ctx);
|
|
833
866
|
return;
|
|
867
|
+
}
|
|
834
868
|
case "Component":
|
|
835
869
|
case "SvelteComponent":
|
|
836
870
|
case "SvelteSelf":
|
|
@@ -884,7 +918,8 @@ function collectA11y(fragment, source) {
|
|
|
884
918
|
const line = lineOf(source, node.start);
|
|
885
919
|
const attrs = node.attributes;
|
|
886
920
|
const roleAttr = findAttr(attrs, "role");
|
|
887
|
-
const
|
|
921
|
+
const resolved = node.type === "SvelteElement" ? svelteElementTags(node.tag) : [node.name];
|
|
922
|
+
const tag = (resolved?.length === 1 ? resolved[0] : void 0)?.toLowerCase();
|
|
888
923
|
const landmark = resolveLandmark({
|
|
889
924
|
tag,
|
|
890
925
|
roleTokens: roleAttr ? splitTokens(attrTextOf(roleAttr)) : void 0,
|
|
@@ -967,14 +1002,18 @@ function parseFile(source, filename) {
|
|
|
967
1002
|
collectComponents(ast.fragment, components);
|
|
968
1003
|
const images = [];
|
|
969
1004
|
collectImages(ast.fragment, source, images);
|
|
970
|
-
const
|
|
971
|
-
|
|
1005
|
+
const headingAcc = {
|
|
1006
|
+
headings: [],
|
|
1007
|
+
dynamic: false
|
|
1008
|
+
};
|
|
1009
|
+
collectHeadings(ast.fragment, source, headingAcc);
|
|
972
1010
|
return {
|
|
973
1011
|
headTags: heads.flatMap(tagsFromHead),
|
|
974
1012
|
components,
|
|
975
1013
|
imports: collectImports(ast),
|
|
976
1014
|
images,
|
|
977
|
-
headings,
|
|
1015
|
+
headings: headingAcc.headings,
|
|
1016
|
+
dynamicHeading: headingAcc.dynamic,
|
|
978
1017
|
a11y: collectA11y(ast.fragment, source),
|
|
979
1018
|
suppressions: collectSuppressions(source)
|
|
980
1019
|
};
|
|
@@ -1080,6 +1119,7 @@ function resolveComponentPath(source, fromFileRel, aliases) {
|
|
|
1080
1119
|
async function resolveFileTags(rt, cwd, fileRel, parsed, config, depth, visited, cache = /* @__PURE__ */ new Map(), aliases) {
|
|
1081
1120
|
const tags = [...parsed.headTags];
|
|
1082
1121
|
const headings = [];
|
|
1122
|
+
let dynamicHeading = false;
|
|
1083
1123
|
let broad = false;
|
|
1084
1124
|
for (const use of parsed.components) {
|
|
1085
1125
|
const info = parsed.imports.get(use.name);
|
|
@@ -1104,6 +1144,7 @@ async function resolveFileTags(rt, cwd, fileRel, parsed, config, depth, visited,
|
|
|
1104
1144
|
file: childRel
|
|
1105
1145
|
});
|
|
1106
1146
|
headings.push(...child.headings);
|
|
1147
|
+
dynamicHeading = dynamicHeading || childParsed.dynamicHeading || child.dynamicHeading;
|
|
1107
1148
|
continue;
|
|
1108
1149
|
}
|
|
1109
1150
|
}
|
|
@@ -1115,7 +1156,8 @@ async function resolveFileTags(rt, cwd, fileRel, parsed, config, depth, visited,
|
|
|
1115
1156
|
return {
|
|
1116
1157
|
tags,
|
|
1117
1158
|
broad,
|
|
1118
|
-
headings
|
|
1159
|
+
headings,
|
|
1160
|
+
dynamicHeading
|
|
1119
1161
|
};
|
|
1120
1162
|
}
|
|
1121
1163
|
//#endregion
|
|
@@ -1333,6 +1375,7 @@ async function resolveRoute(rt, cwd, pageRel, config, layouts, cache, aliases, a
|
|
|
1333
1375
|
const images = [];
|
|
1334
1376
|
const headings = [];
|
|
1335
1377
|
const componentHeadings = [];
|
|
1378
|
+
let dynamicHeading = false;
|
|
1336
1379
|
const a11yCtx = {
|
|
1337
1380
|
rt,
|
|
1338
1381
|
cwd,
|
|
@@ -1373,6 +1416,7 @@ async function resolveRoute(rt, cwd, pageRel, config, layouts, cache, aliases, a
|
|
|
1373
1416
|
...heading,
|
|
1374
1417
|
file: rel
|
|
1375
1418
|
});
|
|
1419
|
+
dynamicHeading = dynamicHeading || parsed.dynamicHeading;
|
|
1376
1420
|
const resolved = await resolveFileTags(rt, cwd, rel, parsed, config, MAX_DEPTH$1, /* @__PURE__ */ new Set([rel]), cache, aliases);
|
|
1377
1421
|
for (const tag of resolved.tags) {
|
|
1378
1422
|
const stamped = {
|
|
@@ -1388,6 +1432,7 @@ async function resolveRoute(rt, cwd, pageRel, config, layouts, cache, aliases, a
|
|
|
1388
1432
|
else broadInherited = true;
|
|
1389
1433
|
}
|
|
1390
1434
|
componentHeadings.push(...resolved.headings);
|
|
1435
|
+
dynamicHeading = dynamicHeading || resolved.dynamicHeading;
|
|
1391
1436
|
}
|
|
1392
1437
|
if (broadOwn || broadInherited) {
|
|
1393
1438
|
const presence = broadOwn ? "own" : "inherited";
|
|
@@ -1436,7 +1481,8 @@ async function resolveRoute(rt, cwd, pageRel, config, layouts, cache, aliases, a
|
|
|
1436
1481
|
headings: {
|
|
1437
1482
|
route,
|
|
1438
1483
|
headings,
|
|
1439
|
-
componentHeadings
|
|
1484
|
+
componentHeadings,
|
|
1485
|
+
...dynamicHeading ? { dynamicHeading: true } : {}
|
|
1440
1486
|
},
|
|
1441
1487
|
a11y: {
|
|
1442
1488
|
route,
|
|
@@ -2515,7 +2561,8 @@ const KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
2515
2561
|
"rules",
|
|
2516
2562
|
"failOn",
|
|
2517
2563
|
"weights",
|
|
2518
|
-
"overrides"
|
|
2564
|
+
"overrides",
|
|
2565
|
+
"seo"
|
|
2519
2566
|
]);
|
|
2520
2567
|
/**
|
|
2521
2568
|
* Validate one rule setting — the bare severity string or the object form — and
|
|
@@ -2607,6 +2654,15 @@ function validateConfigFile(raw, path) {
|
|
|
2607
2654
|
});
|
|
2608
2655
|
config.overrides = overrides;
|
|
2609
2656
|
}
|
|
2657
|
+
if (raw.seo !== void 0) {
|
|
2658
|
+
if (!isPlainObject(raw.seo)) throw new Error(`${path}: seo must be an object, e.g. { indexable: false }.`);
|
|
2659
|
+
const unknown = Object.keys(raw.seo).filter((k) => k !== "indexable");
|
|
2660
|
+
if (unknown.length > 0) throw new Error(`${path}: unknown key(s) in seo: ${unknown.join(", ")}. Known keys: indexable.`);
|
|
2661
|
+
if (raw.seo.indexable !== void 0) {
|
|
2662
|
+
if (typeof raw.seo.indexable !== "boolean") throw new Error(`${path}: seo.indexable must be a boolean.`);
|
|
2663
|
+
config.seo = { indexable: raw.seo.indexable };
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2610
2666
|
if (raw.weights !== void 0) {
|
|
2611
2667
|
if (!isPlainObject(raw.weights)) throw new Error(`${path}: weights must be an object of category → number.`);
|
|
2612
2668
|
const weights = {};
|
|
@@ -2855,18 +2911,19 @@ async function analyzeProject(opts = {}) {
|
|
|
2855
2911
|
const loaded = opts.loadedConfig !== void 0 ? opts.loadedConfig ?? void 0 : opts.configPath !== void 0 ? await loadConfigFromPath(resolve(opts.configPath)) : await loadConfigFile(cwd);
|
|
2856
2912
|
const file = loaded?.config;
|
|
2857
2913
|
const weights = opts.weights ?? file?.weights;
|
|
2914
|
+
const seo = opts.seo ?? file?.seo;
|
|
2858
2915
|
const config = defineConfig({
|
|
2859
2916
|
treatDynamicAs: opts.treatDynamicAs ?? file?.treatDynamicAs ?? "pass",
|
|
2860
2917
|
metaComponents: opts.metaComponents ?? file?.metaComponents ?? [],
|
|
2861
2918
|
rules: resolveRuleSelection({
|
|
2862
|
-
|
|
2863
|
-
rules: opts.rules,
|
|
2919
|
+
rules: withIndexableOff(opts.rules ?? file?.rules, seo),
|
|
2864
2920
|
allowRules: opts.allowRules,
|
|
2865
2921
|
ignoreRules: opts.ignoreRules
|
|
2866
2922
|
}),
|
|
2867
2923
|
failOn: opts.failOn ?? file?.failOn ?? "critical",
|
|
2868
2924
|
...weights !== void 0 ? { weights } : {},
|
|
2869
|
-
...file?.overrides !== void 0 ? { overrides: file.overrides } : {}
|
|
2925
|
+
...file?.overrides !== void 0 ? { overrides: file.overrides } : {},
|
|
2926
|
+
...seo !== void 0 ? { seo } : {}
|
|
2870
2927
|
});
|
|
2871
2928
|
await detectProject(rt, cwd);
|
|
2872
2929
|
const warnings = [
|
|
@@ -3154,4 +3211,4 @@ async function run(opts = {}) {
|
|
|
3154
3211
|
}
|
|
3155
3212
|
}
|
|
3156
3213
|
//#endregion
|
|
3157
|
-
export {
|
|
3214
|
+
export { ProjectError as C, routeMatcher as S, readCoreVersion as _, spinnerEnabled as a, hasDep as b, findUnknownRuleIds as c, ruleOptionsSpec as d, SUPPRESSIONS_FILE as f, isReporterName as g, REPORTER_NAMES as h, run as i, knownRuleIds as l, loadSuppressions as m, applyScope as n, CONFIG_FILENAMES as o, applySuppressions as p, defineConfig$1 as r, loadConfigFile as s, analyzeProject as t, registryTag as u, readPackageVersion as v, createNodeRuntime as w, readPkg as x, discoverApps as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "A deterministic SvelteKit code-health scanner (SEO, performance, correctness, security, architecture, accessibility).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"dist"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@svelte-vitals/core": "0.
|
|
47
|
-
"@clack/prompts": "^1.8.
|
|
48
|
-
"@gunshi/plugin-completion": "0.37.
|
|
49
|
-
"@gunshi/plugin-i18n": "0.37.
|
|
50
|
-
"@gunshi/plugin-suggestion": "0.37.
|
|
51
|
-
"gunshi": "0.37.
|
|
52
|
-
"magicast": "^0.5.
|
|
46
|
+
"@svelte-vitals/core": "0.55.0",
|
|
47
|
+
"@clack/prompts": "^1.8.1",
|
|
48
|
+
"@gunshi/plugin-completion": "0.37.3",
|
|
49
|
+
"@gunshi/plugin-i18n": "0.37.3",
|
|
50
|
+
"@gunshi/plugin-suggestion": "0.37.3",
|
|
51
|
+
"gunshi": "0.37.3",
|
|
52
|
+
"magicast": "^0.5.5",
|
|
53
53
|
"svelte": "^5.57.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@gunshi/docs": "0.37.
|
|
56
|
+
"@gunshi/docs": "0.37.3",
|
|
57
57
|
"@types/estree": "^1.0.9",
|
|
58
|
-
"@types/node": "^24.13.
|
|
58
|
+
"@types/node": "^24.13.5"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|