svelte-vitals 0.33.0 → 0.35.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
@@ -9,7 +9,7 @@ import {
9
9
  readCoreVersion,
10
10
  readPackageVersion,
11
11
  run
12
- } from "./chunk-CWCO6A5B.js";
12
+ } from "./chunk-D6AUX2GC.js";
13
13
 
14
14
  // src/bin.ts
15
15
  import mri3 from "mri";
@@ -408,9 +408,20 @@ var CATEGORY_LABELS = {
408
408
  function oneLine(text) {
409
409
  return text.replace(/\r?\n+/g, " ").trim();
410
410
  }
411
+ function isEmptyDefault(spec) {
412
+ if (spec.kind === "string-list") return spec.default.length === 0;
413
+ if (spec.kind === "string-map") return Object.keys(spec.default).length === 0;
414
+ return false;
415
+ }
416
+ function isInertUntilConfigured(rule) {
417
+ if (!rule.options) return false;
418
+ const specs = Object.values(rule.options);
419
+ return specs.length > 0 && specs.every(isEmptyDefault);
420
+ }
411
421
  function ruleLine(rule) {
412
422
  const fixPart = rule.fix?.description ? ` Fix: ${oneLine(rule.fix.description)}` : "";
413
- return `- **${rule.id} \u2014 ${oneLine(rule.title)}** (${rule.severity}): ${oneLine(rule.rationale)}${fixPart} ([docs](${docsUrlFor(rule.id)}))`;
423
+ const inertPart = isInertUntilConfigured(rule) ? " (inert until configured)" : "";
424
+ return `- **${rule.id} \u2014 ${oneLine(rule.title)}** (${rule.severity}): ${oneLine(rule.rationale)}${fixPart}${inertPart} ([docs](${docsUrlFor(rule.id)}))`;
414
425
  }
415
426
  function ruleDigest() {
416
427
  return CATEGORY_ORDER.map((category) => {
@@ -17,8 +17,7 @@ import {
17
17
  defineConfig,
18
18
  selectRules,
19
19
  applyRuleSeverities,
20
- applyOverrides,
21
- collectKitModuleFacts
20
+ applyOverrides
22
21
  } from "@svelte-vitals/core";
23
22
 
24
23
  // src/runtime/node.ts
@@ -47,14 +46,15 @@ function createNodeRuntime() {
47
46
  };
48
47
  }
49
48
 
50
- // src/providers/source/routes.ts
51
- import { defaultConfig } from "@svelte-vitals/core";
52
-
53
49
  // src/providers/source/project.ts
54
50
  import {
55
51
  ROBOTS_SOURCE_PATHS,
56
52
  SITEMAP_SOURCE_PATHS,
57
- findMinifyDisabled
53
+ SVELTE_CONFIG_FILES,
54
+ VITE_CONFIG_FILES,
55
+ findMinifyDisabled,
56
+ resolveKitAliases,
57
+ resolveKitPathsBase
58
58
  } from "@svelte-vitals/core";
59
59
  var ProjectError = class extends Error {
60
60
  constructor(message) {
@@ -143,14 +143,6 @@ async function robotsRefsSitemap(rt, cwd) {
143
143
  return void 0;
144
144
  }
145
145
  }
146
- var VITE_CONFIG_FILES = [
147
- "vite.config.js",
148
- "vite.config.mjs",
149
- "vite.config.ts",
150
- "vite.config.cjs",
151
- "vite.config.mts",
152
- "vite.config.cts"
153
- ];
154
146
  async function detectViteMinifyDisabled(rt, cwd) {
155
147
  const exists = await Promise.all(VITE_CONFIG_FILES.map((f) => rt.exists(rt.join(cwd, f))));
156
148
  const file = VITE_CONFIG_FILES[exists.indexOf(true)];
@@ -162,12 +154,37 @@ async function detectViteMinifyDisabled(rt, cwd) {
162
154
  return void 0;
163
155
  }
164
156
  }
157
+ async function readFirstConfig(rt, cwd, files) {
158
+ for (const file of files) {
159
+ const path = rt.join(cwd, file);
160
+ if (!await rt.exists(path)) continue;
161
+ try {
162
+ return { file, source: await rt.readFile(path) };
163
+ } catch {
164
+ return void 0;
165
+ }
166
+ }
167
+ return void 0;
168
+ }
169
+ async function detectKitConfigFacts(rt, cwd) {
170
+ const [viteConfig, svelteConfig] = await Promise.all([
171
+ readFirstConfig(rt, cwd, VITE_CONFIG_FILES),
172
+ readFirstConfig(rt, cwd, SVELTE_CONFIG_FILES)
173
+ ]);
174
+ const kitPathsBase = resolveKitPathsBase(viteConfig, svelteConfig);
175
+ const kitAliases = resolveKitAliases(viteConfig, svelteConfig);
176
+ return {
177
+ ...kitPathsBase ? { kitPathsBase } : {},
178
+ ...kitAliases ? { kitAliases } : {}
179
+ };
180
+ }
165
181
  async function collectProjectFacts(rt, cwd) {
166
- const [hasRobotsTxt, hasSitemap, htmlLang, viteMinifyDisabled] = await Promise.all([
182
+ const [hasRobotsTxt, hasSitemap, htmlLang, viteMinifyDisabled, kitConfig] = await Promise.all([
167
183
  existsAny(rt, cwd, ROBOTS_SOURCE_PATHS),
168
184
  existsAny(rt, cwd, SITEMAP_SOURCE_PATHS),
169
185
  detectAppHtmlLang(rt, cwd),
170
- detectViteMinifyDisabled(rt, cwd)
186
+ detectViteMinifyDisabled(rt, cwd),
187
+ detectKitConfigFacts(rt, cwd)
171
188
  ]);
172
189
  const robotsReferencesSitemap = await robotsRefsSitemap(rt, cwd);
173
190
  return {
@@ -175,10 +192,23 @@ async function collectProjectFacts(rt, cwd) {
175
192
  hasSitemap,
176
193
  htmlLang,
177
194
  ...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {},
178
- ...viteMinifyDisabled ? { viteMinifyDisabled } : {}
195
+ ...viteMinifyDisabled ? { viteMinifyDisabled } : {},
196
+ ...kitConfig
179
197
  };
180
198
  }
181
199
 
200
+ // src/collect-all.ts
201
+ import {
202
+ collectKitModuleFacts,
203
+ collectSourceFiles
204
+ } from "@svelte-vitals/core";
205
+
206
+ // src/providers/source/components.ts
207
+ import { collectComponentFacts } from "@svelte-vitals/core";
208
+
209
+ // src/providers/source/routes.ts
210
+ import { defaultConfig } from "@svelte-vitals/core";
211
+
182
212
  // src/providers/source/adapters/svelte-meta-tags.ts
183
213
  import { attrValueOf, attrTextOf } from "@svelte-vitals/core";
184
214
 
@@ -707,8 +737,27 @@ async function collectRoutes(rt, cwd, config = defaultConfig, cache = /* @__PURE
707
737
  };
708
738
  }
709
739
 
710
- // src/providers/source/components.ts
711
- import { collectComponentFacts } from "@svelte-vitals/core";
740
+ // src/route-matcher.ts
741
+ function routeMatcher(glob2) {
742
+ if (!glob2) return () => true;
743
+ const body = glob2.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, " ").replace(/\*/g, "[^/]*").replace(/\/ $/g, "(?:/.*)?").replace(/^ \//g, "(?:.*/)?").replace(/ \//g, "(?:.*/)?").replace(/\/ /g, "(?:/.*)?").replace(/ /g, ".*");
744
+ const re = new RegExp(`^${body}$`);
745
+ return (route) => re.test(route.replace(/^\//, ""));
746
+ }
747
+
748
+ // src/collect-all.ts
749
+ async function collectAll(rt, cwd, config, opts = {}) {
750
+ const matches = routeMatcher(opts.route);
751
+ const collected = await collectRoutes(rt, cwd, config, opts.parseCache);
752
+ const heads = collected.heads.filter((h) => matches(h.route));
753
+ const images = collected.images.filter((i) => matches(i.route));
754
+ const headings = collected.headings.filter((h) => matches(h.route));
755
+ const project = await collectProjectFacts(rt, cwd);
756
+ const components = opts.route ? [] : await collectComponentFacts(rt, cwd);
757
+ const kitModules = opts.route ? [] : await collectKitModuleFacts(rt, cwd, project.kitAliases);
758
+ const sourceFiles = opts.route ? void 0 : await collectSourceFiles(rt, cwd);
759
+ return { heads, images, headings, project, components, kitModules, sourceFiles };
760
+ }
712
761
 
713
762
  // src/discover-apps.ts
714
763
  import { existsSync } from "fs";
@@ -1167,16 +1216,27 @@ async function playMascotGreeting(opts) {
1167
1216
  import { existsSync as existsSync2 } from "fs";
1168
1217
  import { join as join5 } from "path";
1169
1218
  import { pathToFileURL } from "url";
1219
+ import {
1220
+ CATEGORIES,
1221
+ defaultConfig as defaultConfig2,
1222
+ resolveRuleOptions,
1223
+ shouldSkipRangeCheck,
1224
+ validateRuleSetting
1225
+ } from "@svelte-vitals/core";
1170
1226
 
1171
1227
  // src/rules-config.ts
1172
1228
  import { allRules } from "@svelte-vitals/core";
1173
1229
  var KNOWN_IDS = new Set(allRules.map((r) => r.id));
1230
+ var RULE_BY_ID = new Map(allRules.map((r) => [r.id, r]));
1174
1231
  function findUnknownRuleIds(ids) {
1175
1232
  return [...new Set(ids.filter((id) => !KNOWN_IDS.has(id)))];
1176
1233
  }
1177
1234
  function knownRuleIds() {
1178
1235
  return [...KNOWN_IDS].sort();
1179
1236
  }
1237
+ function ruleOptionsSpec(id) {
1238
+ return RULE_BY_ID.get(id)?.options;
1239
+ }
1180
1240
  function buildRulesConfig(allow, ignore) {
1181
1241
  const rules = {};
1182
1242
  if (allow.length > 0) {
@@ -1188,17 +1248,23 @@ function buildRulesConfig(allow, ignore) {
1188
1248
 
1189
1249
  // src/config-file.ts
1190
1250
  var CONFIG_FILENAMES = ["svelte-vitals.config.mjs", "svelte-vitals.config.js", "svelte-vitals.config.ts"];
1191
- var CATEGORIES = ["seo", "performance", "correctness", "security", "architecture"];
1192
1251
  var TREAT_DYNAMIC_AS_VALUES = ["pass", "warn", "fail"];
1193
1252
  var FAIL_ON_VALUES = ["critical", "warning", "info"];
1194
1253
  var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["treatDynamicAs", "metaComponents", "rules", "failOn", "weights", "overrides"]);
1195
- var RULE_SETTING_VALUES = ["off", "critical", "warning", "info"];
1196
1254
  function isPlainObject2(value) {
1197
1255
  return typeof value === "object" && value !== null && !Array.isArray(value);
1198
1256
  }
1199
1257
  function isMissingExtensionLoaderError(err) {
1200
1258
  return err instanceof Error && ("code" in err && err.code === "ERR_UNKNOWN_FILE_EXTENSION" || /Unknown file extension/.test(err.message));
1201
1259
  }
1260
+ function validateSetting(path, where, key, setting, allowOptions, baseline, skipRangeCheck) {
1261
+ const errors = validateRuleSetting(`${where}.${key}`, key, setting, ruleOptionsSpec(key), {
1262
+ allowOptions,
1263
+ ...baseline !== void 0 ? { baseline } : {},
1264
+ ...skipRangeCheck !== void 0 ? { skipRangeCheck } : {}
1265
+ });
1266
+ if (errors.length > 0) throw new Error(`${path}: ${errors.join(" ")}`);
1267
+ }
1202
1268
  function validateConfigFile(raw, path) {
1203
1269
  const warnings = [];
1204
1270
  const config = {};
@@ -1241,6 +1307,7 @@ function validateConfigFile(raw, path) {
1241
1307
  `${path}: unknown rule id(s) in rules: ${unknown.join(", ")}. Known rule ids: ${knownRuleIds().join(", ")}`
1242
1308
  );
1243
1309
  }
1310
+ for (const [key, setting] of Object.entries(rules)) validateSetting(path, "rules", key, setting, true);
1244
1311
  config.rules = rules;
1245
1312
  }
1246
1313
  if (raw.overrides !== void 0) {
@@ -1250,7 +1317,8 @@ function validateConfigFile(raw, path) {
1250
1317
  const isGlob = (v) => typeof v === "string" && v.length > 0;
1251
1318
  const isGlobs = (v) => isGlob(v) || Array.isArray(v) && v.length > 0 && v.every(isGlob);
1252
1319
  const overrides = [];
1253
- raw.overrides.forEach((entry, i) => {
1320
+ const rawOverrides = raw.overrides;
1321
+ rawOverrides.forEach((entry, i) => {
1254
1322
  if (!isPlainObject2(entry)) {
1255
1323
  throw new Error(`${path}: overrides[${i}] must be an object with 'route' and/or 'files', and 'rules'.`);
1256
1324
  }
@@ -1281,11 +1349,10 @@ function validateConfigFile(raw, path) {
1281
1349
  );
1282
1350
  }
1283
1351
  for (const [key, setting] of Object.entries(entry.rules)) {
1284
- if (!RULE_SETTING_VALUES.includes(setting)) {
1285
- throw new Error(
1286
- `${path}: overrides[${i}].rules.${key}: invalid setting '${String(setting)}'; expected ${RULE_SETTING_VALUES.join("|")}.`
1287
- );
1288
- }
1352
+ const isCategory = CATEGORIES.includes(key);
1353
+ const baseline = isCategory ? void 0 : resolveRuleOptions(key, ruleOptionsSpec(key), { ...defaultConfig2, rules: config.rules ?? {} });
1354
+ const skipRangeCheck = shouldSkipRangeCheck(rawOverrides, i, key, setting);
1355
+ validateSetting(path, `overrides[${i}].rules`, key, setting, !isCategory, baseline, skipRangeCheck);
1289
1356
  }
1290
1357
  overrides.push({
1291
1358
  ...entry.route !== void 0 ? { route: entry.route } : {},
@@ -1397,12 +1464,6 @@ import { defineConfig as defineConfig2 } from "@svelte-vitals/core";
1397
1464
  function spinnerEnabled(opts) {
1398
1465
  return opts.reporter === "console" && opts.stderrIsTTY && !isAutoDetectedAgent(opts.rawReporter, opts.env) && colorEnabled({ reporter: opts.reporter, isTTY: opts.stderrIsTTY, env: opts.env, noColorFlag: opts.noColorFlag });
1399
1466
  }
1400
- function routeMatcher(glob2) {
1401
- if (!glob2) return () => true;
1402
- const body = glob2.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, " ").replace(/\*/g, "[^/]*").replace(/\/ $/g, "(?:/.*)?").replace(/^ \//g, "(?:.*/)?").replace(/ \//g, "(?:.*/)?").replace(/\/ /g, "(?:/.*)?").replace(/ /g, ".*");
1403
- const re = new RegExp(`^${body}$`);
1404
- return (route) => re.test(route.replace(/^\//, ""));
1405
- }
1406
1467
  async function analyzeProject(opts = {}) {
1407
1468
  const cwd = opts.cwd ?? process.cwd();
1408
1469
  const rt = createNodeRuntime();
@@ -1419,19 +1480,15 @@ async function analyzeProject(opts = {}) {
1419
1480
  });
1420
1481
  await detectProject(rt, cwd);
1421
1482
  const warnings = [...loaded?.warnings ?? [], ...await checkVersionFloor(rt, cwd)];
1422
- const matches = routeMatcher(opts.route);
1423
- const collected = await collectRoutes(rt, cwd, config, opts.parseCache);
1424
- const heads = collected.heads.filter((h) => matches(h.route));
1425
- const images = collected.images.filter((i) => matches(i.route));
1426
- const headings = collected.headings.filter((h) => matches(h.route));
1427
- const project = await collectProjectFacts(rt, cwd);
1428
- const components = opts.route ? [] : await collectComponentFacts(rt, cwd);
1429
- const kitModules = opts.route ? [] : await collectKitModuleFacts(rt, cwd);
1483
+ const { heads, images, headings, project, components, kitModules, sourceFiles } = await collectAll(rt, cwd, config, {
1484
+ route: opts.route,
1485
+ parseCache: opts.parseCache
1486
+ });
1430
1487
  const selected = selectRules(allRules2, config);
1431
1488
  const rules = opts.categories ? selected.filter((r) => opts.categories.includes(r.category)) : selected;
1432
1489
  const results = applyOverrides(
1433
1490
  applyRuleSeverities(
1434
- await runRules(rules, { heads, images, headings, components, project, config, kitModules }),
1491
+ await runRules(rules, { heads, images, headings, components, project, config, kitModules, sourceFiles }),
1435
1492
  config
1436
1493
  ),
1437
1494
  config
@@ -1684,17 +1741,18 @@ async function run(opts = {}) {
1684
1741
 
1685
1742
  export {
1686
1743
  ProjectError,
1744
+ routeMatcher,
1687
1745
  discoverApps,
1688
1746
  readPackageVersion,
1689
1747
  readCoreVersion,
1690
1748
  isReporterName,
1691
1749
  findUnknownRuleIds,
1692
1750
  knownRuleIds,
1751
+ ruleOptionsSpec,
1693
1752
  buildRulesConfig,
1694
1753
  CONFIG_FILENAMES,
1695
1754
  loadConfigFile,
1696
1755
  spinnerEnabled,
1697
- routeMatcher,
1698
1756
  analyzeProject,
1699
1757
  applyScope,
1700
1758
  run,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { HeadTag, RuleSetting, Config, Severity, Category, Result } from '@svelte-vitals/core';
1
+ import { HeadTag, RuleSetting, RuleOptionsSpec, Config, Severity, Category, Result } from '@svelte-vitals/core';
2
2
  export { defineConfig } from '@svelte-vitals/core';
3
3
  import { AST } from 'svelte/compiler';
4
4
 
@@ -56,10 +56,14 @@ declare class ProjectError extends Error {
56
56
  constructor(message: string);
57
57
  }
58
58
 
59
+ declare function routeMatcher(glob: string | undefined): (route: string) => boolean;
60
+
59
61
  /** Rule ids passed to --rules/--ignore that aren't part of the built-in registry. */
60
62
  declare function findUnknownRuleIds(ids: string[]): string[];
61
63
  /** All built-in rule ids, sorted — for help and error messages. */
62
64
  declare function knownRuleIds(): string[];
65
+ /** The options a rule declares, or undefined when it takes none. */
66
+ declare function ruleOptionsSpec(id: string): RuleOptionsSpec | undefined;
63
67
  /**
64
68
  * Build the per-rule config map from an allow-list (--rules) and a deny-list
65
69
  * (--ignore). An allow-list disables every rule not listed; deny always wins.
@@ -166,7 +170,6 @@ declare function spinnerEnabled(opts: {
166
170
  env: NodeJS.ProcessEnv;
167
171
  noColorFlag?: boolean;
168
172
  }): boolean;
169
- declare function routeMatcher(glob: string | undefined): (route: string) => boolean;
170
173
  interface AnalyzeOptions {
171
174
  cwd?: string;
172
175
  metaComponents?: string[];
@@ -243,4 +246,4 @@ declare function applyScope(results: Result[], opts: ApplyScopeOptions): Promise
243
246
  */
244
247
  declare function run(opts?: RunOptions): Promise<number>;
245
248
 
246
- export { type AnalyzeOptions, type AnalyzeResult, type ApplyScopeOptions, type LoadedConfigFile, type ParseCache, ProjectError, type RunOptions, analyzeProject, applyScope, buildRulesConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, routeMatcher, run, spinnerEnabled };
249
+ export { type AnalyzeOptions, type AnalyzeResult, type ApplyScopeOptions, type LoadedConfigFile, type ParseCache, ProjectError, type RunOptions, analyzeProject, applyScope, buildRulesConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
package/dist/index.js CHANGED
@@ -8,9 +8,10 @@ import {
8
8
  knownRuleIds,
9
9
  loadConfigFile,
10
10
  routeMatcher,
11
+ ruleOptionsSpec,
11
12
  run,
12
13
  spinnerEnabled
13
- } from "./chunk-CWCO6A5B.js";
14
+ } from "./chunk-D6AUX2GC.js";
14
15
  export {
15
16
  ProjectError,
16
17
  analyzeProject,
@@ -21,6 +22,7 @@ export {
21
22
  knownRuleIds,
22
23
  loadConfigFile,
23
24
  routeMatcher,
25
+ ruleOptionsSpec,
24
26
  run,
25
27
  spinnerEnabled
26
28
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-vitals",
3
- "version": "0.33.0",
3
+ "version": "0.35.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",
@@ -43,10 +43,10 @@
43
43
  "log-update": "^8.0.0",
44
44
  "magicast": "^0.5.3",
45
45
  "mri": "^1.2.0",
46
- "smol-toml": "^1.7.0",
47
- "svelte": "^5.56.6",
46
+ "smol-toml": "^1.7.1",
47
+ "svelte": "^5.56.8",
48
48
  "tinyglobby": "^0.2.17",
49
- "@svelte-vitals/core": "0.29.0"
49
+ "@svelte-vitals/core": "0.31.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/estree": "^1.0.9",
@@ -56,6 +56,7 @@
56
56
  "build": "tsup",
57
57
  "typecheck": "tsc --noEmit",
58
58
  "test": "vitest run",
59
+ "gen:rules-index": "pnpm --filter @svelte-vitals/core build && node scripts/gen-rules-index.mjs",
59
60
  "update-action-pin": "node scripts/gen-action-pin.mjs"
60
61
  }
61
62
  }