svelte-vitals 0.28.0 → 0.30.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-O5VMXV2Q.js";
12
+ } from "./chunk-QBXO46PU.js";
13
13
 
14
14
  // src/bin.ts
15
15
  import mri3 from "mri";
@@ -998,8 +998,8 @@ function readInstalledViteVersion(io) {
998
998
  }
999
999
 
1000
1000
  // src/ci/action-pin.generated.ts
1001
- var ACTION_SHA = "90b7538b5bed65ac123f46d77511dd4b48fa69bb";
1002
- var ACTION_VERSION = "0.3.3";
1001
+ var ACTION_SHA = "81d49a11b71a16ea1294212d43520a0137b4bd5e";
1002
+ var ACTION_VERSION = "0.3.5";
1003
1003
 
1004
1004
  // src/install/index.ts
1005
1005
  function detectPackageManagerNear(io, appDir) {
@@ -1195,9 +1195,15 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1195
1195
  io.errorLog("svelte-vitals: no valid clients or targets selected.");
1196
1196
  return 2;
1197
1197
  }
1198
- const hasSvelteConfig = (dir) => {
1198
+ const isSvelteKitApp = (dir) => {
1199
1199
  try {
1200
- return io.readFile(join4(dir, "svelte.config.js")) !== void 0 || io.readFile(join4(dir, "svelte.config.ts")) !== void 0;
1200
+ if (io.readFile(join4(dir, "svelte.config.js")) !== void 0 || io.readFile(join4(dir, "svelte.config.ts")) !== void 0) {
1201
+ return true;
1202
+ }
1203
+ const pkgRaw = io.readFile(join4(dir, "package.json"));
1204
+ if (pkgRaw === void 0) return false;
1205
+ const pkg = JSON.parse(pkgRaw);
1206
+ return Boolean(pkg.dependencies?.["@sveltejs/kit"] ?? pkg.devDependencies?.["@sveltejs/kit"]);
1201
1207
  } catch {
1202
1208
  return false;
1203
1209
  }
@@ -1207,12 +1213,14 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1207
1213
  if (needsApp) {
1208
1214
  if (flags.app) {
1209
1215
  const candidate = join4(io.cwd, flags.app);
1210
- if (!hasSvelteConfig(candidate)) {
1211
- io.errorLog(`svelte-vitals: --app '${flags.app}' is not a SvelteKit app (no svelte.config.{js,ts} there).`);
1216
+ if (!isSvelteKitApp(candidate)) {
1217
+ io.errorLog(
1218
+ `svelte-vitals: --app '${flags.app}' is not a SvelteKit app (no svelte.config.{js,ts} or @sveltejs/kit dependency there).`
1219
+ );
1212
1220
  return 2;
1213
1221
  }
1214
1222
  appDir = candidate;
1215
- } else if (!hasSvelteConfig(io.cwd)) {
1223
+ } else if (!isSvelteKitApp(io.cwd)) {
1216
1224
  const apps = await (io.discoverApps ?? discoverApps)(io.cwd);
1217
1225
  if (apps.length === 1) {
1218
1226
  io.errorLog(`svelte-vitals: detected SvelteKit app at ${apps[0]}; targeting it for the Vite/config targets.`);
@@ -16,7 +16,9 @@ import {
16
16
  computeHealth,
17
17
  defineConfig,
18
18
  selectRules,
19
- applyRuleSeverities
19
+ applyRuleSeverities,
20
+ applyOverrides,
21
+ collectKitModuleFacts
20
22
  } from "@svelte-vitals/core";
21
23
 
22
24
  // src/runtime/node.ts
@@ -652,16 +654,36 @@ import { collectComponentFacts } from "@svelte-vitals/core";
652
654
 
653
655
  // src/discover-apps.ts
654
656
  import { existsSync } from "fs";
657
+ import { readFile as readFile2 } from "fs/promises";
655
658
  import { join as join2, dirname } from "path";
656
659
  import { glob } from "tinyglobby";
660
+ var GLOB_OPTS = {
661
+ dot: false,
662
+ deep: 4,
663
+ ignore: ["**/node_modules/**", "**/.svelte-kit/**", "**/build/**", "**/dist/**", "**/.git/**"]
664
+ };
665
+ async function hasKitDependency(pkgJsonPath) {
666
+ try {
667
+ const pkg = JSON.parse(await readFile2(pkgJsonPath, "utf8"));
668
+ return Boolean(pkg.dependencies?.["@sveltejs/kit"] ?? pkg.devDependencies?.["@sveltejs/kit"]);
669
+ } catch {
670
+ return false;
671
+ }
672
+ }
657
673
  async function discoverApps(cwd) {
658
- const configs = await glob("**/svelte.config.{js,ts}", {
659
- cwd,
660
- dot: false,
661
- deep: 4,
662
- ignore: ["**/node_modules/**", "**/.svelte-kit/**", "**/build/**", "**/dist/**", "**/.git/**"]
663
- });
664
- const dirs = [...new Set(configs.map((c) => dirname(c)))].filter(
674
+ const [configs, pkgJsons] = await Promise.all([
675
+ glob("**/svelte.config.{js,ts}", { cwd, ...GLOB_OPTS }),
676
+ glob("**/package.json", { cwd, ...GLOB_OPTS })
677
+ ]);
678
+ const configDirs = configs.map(dirname);
679
+ const kitDepDirs = [];
680
+ for (const pkgJson of pkgJsons) {
681
+ const dir = dirname(pkgJson);
682
+ if (dir !== "." && await hasKitDependency(join2(cwd, pkgJson))) {
683
+ kitDepDirs.push(dir);
684
+ }
685
+ }
686
+ const dirs = [.../* @__PURE__ */ new Set([...configDirs, ...kitDepDirs])].filter(
665
687
  (d) => d !== "." && existsSync(join2(cwd, d, "src", "routes"))
666
688
  );
667
689
  return dirs.sort();
@@ -1111,7 +1133,8 @@ var CONFIG_FILENAMES = ["svelte-vitals.config.mjs", "svelte-vitals.config.js", "
1111
1133
  var CATEGORIES = ["seo", "performance", "correctness", "security", "architecture"];
1112
1134
  var TREAT_DYNAMIC_AS_VALUES = ["pass", "warn", "fail"];
1113
1135
  var FAIL_ON_VALUES = ["critical", "warning", "info"];
1114
- var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["treatDynamicAs", "metaComponents", "rules", "failOn", "weights"]);
1136
+ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["treatDynamicAs", "metaComponents", "rules", "failOn", "weights", "overrides"]);
1137
+ var RULE_SETTING_VALUES = ["off", "critical", "warning", "info"];
1115
1138
  function isPlainObject2(value) {
1116
1139
  return typeof value === "object" && value !== null && !Array.isArray(value);
1117
1140
  }
@@ -1162,6 +1185,58 @@ function validateConfigFile(raw, path) {
1162
1185
  }
1163
1186
  config.rules = rules;
1164
1187
  }
1188
+ if (raw.overrides !== void 0) {
1189
+ if (!Array.isArray(raw.overrides)) {
1190
+ throw new Error(`${path}: overrides must be an array of { route/files, rules } entries.`);
1191
+ }
1192
+ const isGlob = (v) => typeof v === "string" && v.length > 0;
1193
+ const isGlobs = (v) => isGlob(v) || Array.isArray(v) && v.length > 0 && v.every(isGlob);
1194
+ const overrides = [];
1195
+ raw.overrides.forEach((entry, i) => {
1196
+ if (!isPlainObject2(entry)) {
1197
+ throw new Error(`${path}: overrides[${i}] must be an object with 'route' and/or 'files', and 'rules'.`);
1198
+ }
1199
+ if (entry.route !== void 0 && !isGlobs(entry.route)) {
1200
+ throw new Error(
1201
+ `${path}: overrides[${i}].route must be a non-empty string or a non-empty array of non-empty strings.`
1202
+ );
1203
+ }
1204
+ if (entry.files !== void 0 && !isGlobs(entry.files)) {
1205
+ throw new Error(
1206
+ `${path}: overrides[${i}].files must be a non-empty string or a non-empty array of non-empty strings.`
1207
+ );
1208
+ }
1209
+ if (entry.route === void 0 && entry.files === void 0) {
1210
+ throw new Error(`${path}: overrides[${i}] must set 'route' and/or 'files' to scope the override.`);
1211
+ }
1212
+ if (!isPlainObject2(entry.rules)) {
1213
+ throw new Error(`${path}: overrides[${i}].rules must be an object of rule-id/category \u2192 setting.`);
1214
+ }
1215
+ if (Object.keys(entry.rules).length === 0) {
1216
+ throw new Error(`${path}: overrides[${i}].rules must contain at least one rule id or category.`);
1217
+ }
1218
+ const nonCategoryKeys = Object.keys(entry.rules).filter((k) => !CATEGORIES.includes(k));
1219
+ const unknown = findUnknownRuleIds(nonCategoryKeys);
1220
+ if (unknown.length > 0) {
1221
+ throw new Error(
1222
+ `${path}: unknown rule id(s) or categories in overrides[${i}].rules: ${unknown.join(", ")}. Known categories: ${CATEGORIES.join(", ")}. Known rule ids: ${knownRuleIds().join(", ")}`
1223
+ );
1224
+ }
1225
+ for (const [key, setting] of Object.entries(entry.rules)) {
1226
+ if (!RULE_SETTING_VALUES.includes(setting)) {
1227
+ throw new Error(
1228
+ `${path}: overrides[${i}].rules.${key}: invalid setting '${String(setting)}'; expected ${RULE_SETTING_VALUES.join("|")}.`
1229
+ );
1230
+ }
1231
+ }
1232
+ overrides.push({
1233
+ ...entry.route !== void 0 ? { route: entry.route } : {},
1234
+ ...entry.files !== void 0 ? { files: entry.files } : {},
1235
+ rules: entry.rules
1236
+ });
1237
+ });
1238
+ config.overrides = overrides;
1239
+ }
1165
1240
  if (raw.weights !== void 0) {
1166
1241
  if (!isPlainObject2(raw.weights)) {
1167
1242
  throw new Error(`${path}: weights must be an object of category \u2192 number.`);
@@ -1282,7 +1357,8 @@ async function analyzeProject(opts = {}) {
1282
1357
  metaComponents: opts.metaComponents ?? file?.metaComponents ?? [],
1283
1358
  rules: opts.rules ?? file?.rules ?? {},
1284
1359
  failOn: opts.failOn ?? file?.failOn ?? "critical",
1285
- ...weights !== void 0 ? { weights } : {}
1360
+ ...weights !== void 0 ? { weights } : {},
1361
+ ...file?.overrides !== void 0 ? { overrides: file.overrides } : {}
1286
1362
  });
1287
1363
  await detectProject(rt, cwd);
1288
1364
  const matches = routeMatcher(opts.route);
@@ -1292,10 +1368,14 @@ async function analyzeProject(opts = {}) {
1292
1368
  const headings = collected.headings.filter((h) => matches(h.route));
1293
1369
  const project = await collectProjectFacts(rt, cwd);
1294
1370
  const components = opts.route ? [] : await collectComponentFacts(rt, cwd);
1371
+ const kitModules = opts.route ? [] : await collectKitModuleFacts(rt, cwd);
1295
1372
  const selected = selectRules(allRules2, config);
1296
1373
  const rules = opts.categories ? selected.filter((r) => opts.categories.includes(r.category)) : selected;
1297
- const results = applyRuleSeverities(
1298
- await runRules(rules, { heads, images, headings, components, project, config }),
1374
+ const results = applyOverrides(
1375
+ applyRuleSeverities(
1376
+ await runRules(rules, { heads, images, headings, components, project, config, kitModules }),
1377
+ config
1378
+ ),
1299
1379
  config
1300
1380
  );
1301
1381
  return { results, config, version: readPackageVersion(), warnings };
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  routeMatcher,
11
11
  run,
12
12
  spinnerEnabled
13
- } from "./chunk-O5VMXV2Q.js";
13
+ } from "./chunk-QBXO46PU.js";
14
14
  export {
15
15
  ProjectError,
16
16
  analyzeProject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-vitals",
3
- "version": "0.28.0",
3
+ "version": "0.30.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",
@@ -46,7 +46,7 @@
46
46
  "smol-toml": "^1.7.0",
47
47
  "svelte": "^5.56.4",
48
48
  "tinyglobby": "^0.2.17",
49
- "@svelte-vitals/core": "0.25.0"
49
+ "@svelte-vitals/core": "0.27.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^24.13.3"