svelte-vitals 0.54.7 → 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.
@@ -1,4 +1,4 @@
1
- import { _ as readCoreVersion, c as findUnknownRuleIds, g as isReporterName, i as run, l as knownRuleIds, v as readPackageVersion } from "./src-Oo1FLd1x.js";
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-D5CKrnVf.js";
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-DJIjQlEC.js");
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-CfxdUEEe.js");
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-D9Cc6k2G.js");
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-BIpFmwLJ.js").then((n) => n.n);
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-CVMFzrRD.js");
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-D5CKrnVf.js";
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 { h as REPORTER_NAMES } from "./src-Oo1FLd1x.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-D5CKrnVf.js";
3
- import { t as INSTALL_ARGS } from "./install-BIpFmwLJ.js";
4
- import { CI_ARGS, CI_UPGRADE_ARGS } from "./ci-CVMFzrRD.js";
5
- import { DOCS_LIST_ARGS, DOCS_ROOT_ARGS, DOCS_SHOW_ARGS } from "./docs-CfxdUEEe.js";
6
- import { EXPLAIN_ARGS } from "./explain-D9Cc6k2G.js";
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-Oo1FLd1x.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-D5CKrnVf.js";
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";
@@ -1,5 +1,5 @@
1
- import { l as knownRuleIds } from "./src-Oo1FLd1x.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-D5CKrnVf.js";
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";
@@ -1,4 +1,4 @@
1
- import { t as ROOT_ARGS } from "./analyze-D5CKrnVf.js";
2
- import { t as INSTALL_ARGS } from "./install-BIpFmwLJ.js";
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
@@ -317,6 +319,8 @@ export interface AnalyzeOptions {
317
319
  allowRules?: string[];
318
320
  /** Per-category weights for the combined Health score (flag > config file > default 1 each). */
319
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'];
320
324
  /** Restrict analysis to rules in these categories (applied after rules/ignore selection). */
321
325
  categories?: Category[];
322
326
  /**
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
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-Oo1FLd1x.js";
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
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 { b as hasDep, o as CONFIG_FILENAMES, v as readPackageVersion, x as readPkg, y as discoverApps } from "./src-Oo1FLd1x.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-D5CKrnVf.js";
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
- /** Recursively collect page-body headings (<h1>–<h6>) anywhere in the template (seo/single-h1). */
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" && /^h[1-6]$/.test(node.name)) acc.push({
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
- if (node.type === "SvelteElement") elementsUnknowable = true;
831
- else elementTags.add(node.name.toLowerCase());
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 tag = (node.type === "SvelteElement" ? node.tag.type === "Literal" && typeof node.tag.value === "string" ? node.tag.value : void 0 : node.name)?.toLowerCase();
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 headings = [];
971
- collectHeadings(ast.fragment, source, headings);
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
- fileRules: file?.rules,
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 = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-vitals",
3
- "version": "0.54.7",
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.54.7",
47
- "@clack/prompts": "^1.8.0",
48
- "@gunshi/plugin-completion": "0.37.2",
49
- "@gunshi/plugin-i18n": "0.37.2",
50
- "@gunshi/plugin-suggestion": "0.37.2",
51
- "gunshi": "0.37.2",
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
52
  "magicast": "^0.5.5",
53
53
  "svelte": "^5.57.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@gunshi/docs": "0.37.2",
56
+ "@gunshi/docs": "0.37.3",
57
57
  "@types/estree": "^1.0.9",
58
- "@types/node": "^24.13.4"
58
+ "@types/node": "^24.13.5"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "tsdown",