svelte-vitals 0.32.0 → 0.34.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-WSPRFLPP.js";
13
13
 
14
14
  // src/bin.ts
15
15
  import mri3 from "mri";
@@ -341,12 +341,6 @@ function isConfigTargetId(id) {
341
341
  return CONFIG_TARGETS.some((t) => t.id === id);
342
342
  }
343
343
 
344
- // src/ci/action-pin-comment.ts
345
- function actionPinComment(version) {
346
- return `action-v${version}`;
347
- }
348
- var ACTION_PIN_COMMENT_RE = /#\s*(?:action-v|@svelte-vitals\/action@)(\S+)/;
349
-
350
344
  // src/ci/workflow.ts
351
345
  var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
352
346
  var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
@@ -377,7 +371,7 @@ function buildWorkflowYaml(opts) {
377
371
  ` - uses: actions/checkout@${CHECKOUT_SHA} # ${CHECKOUT_VERSION}`,
378
372
  " with:",
379
373
  " fetch-depth: 0",
380
- ` - uses: oekazuma/svelte-vitals/packages/action@${actionSha} # ${actionPinComment(actionVersion)}`,
374
+ ` - uses: oekazuma/svelte-vitals-action@${actionSha} # v${actionVersion}`,
381
375
  " with:",
382
376
  " diff: origin/${{ github.base_ref }}",
383
377
  " baseline: origin/${{ github.base_ref }}",
@@ -414,9 +408,20 @@ var CATEGORY_LABELS = {
414
408
  function oneLine(text) {
415
409
  return text.replace(/\r?\n+/g, " ").trim();
416
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
+ }
417
421
  function ruleLine(rule) {
418
422
  const fixPart = rule.fix?.description ? ` Fix: ${oneLine(rule.fix.description)}` : "";
419
- 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)}))`;
420
425
  }
421
426
  function ruleDigest() {
422
427
  return CATEGORY_ORDER.map((category) => {
@@ -1004,8 +1009,8 @@ function readInstalledViteVersion(io) {
1004
1009
  }
1005
1010
 
1006
1011
  // src/ci/action-pin.generated.ts
1007
- var ACTION_SHA = "a3af7b5cdb026fc3f6e685b87e964b5190583960";
1008
- var ACTION_VERSION = "0.3.8";
1012
+ var ACTION_SHA = "67ec0af79398845d01e4371d4953d08f6f6a244f";
1013
+ var ACTION_VERSION = "0.4.0";
1009
1014
 
1010
1015
  // src/install/index.ts
1011
1016
  function detectPackageManagerNear(io, appDir) {
@@ -1577,8 +1582,12 @@ import { join as join5 } from "path";
1577
1582
  import mri2 from "mri";
1578
1583
 
1579
1584
  // src/ci/upgrade.ts
1580
- var ACTION_USES_LINE = /^(?<indent>\s*-\s*uses:\s*(?:&\S+\s+)?oekazuma\/svelte-vitals\/packages\/action@)(?<ref>[^\s#]+)(?<comment>\s*#.*)?$/;
1581
- var LEGACY_COMMENT = /#\s*@svelte-vitals\/action@/;
1585
+ var CANONICAL_PATH = "oekazuma/svelte-vitals-action";
1586
+ var ACTION_USES_LINE = /^(?<indent>\s*-\s*uses:\s*(?:&\S+\s+)?)(?<repoPath>oekazuma\/svelte-vitals(?:-action|\/packages\/action))@(?<ref>[^\s#]+)(?<comment>\s*#.*)?$/;
1587
+ var PIN_COMMENT_RE = /#\s*(?:v|action-v|@svelte-vitals\/action@)(\S+)/;
1588
+ function isCanonicalComment(comment, version) {
1589
+ return comment.trim() === `# v${version}`;
1590
+ }
1582
1591
  function upgradeActionPin(content, sha, version) {
1583
1592
  const lines = content.split("\n");
1584
1593
  let replaced = 0;
@@ -1588,17 +1597,16 @@ function upgradeActionPin(content, sha, version) {
1588
1597
  const bare = eol ? line.slice(0, -1) : line;
1589
1598
  const match = ACTION_USES_LINE.exec(bare);
1590
1599
  if (!match || !match.groups) return line;
1591
- const { indent: indent2, ref } = match.groups;
1592
- if (indent2 === void 0 || ref === void 0) return line;
1600
+ const { indent: indent2, repoPath, ref } = match.groups;
1601
+ if (indent2 === void 0 || repoPath === void 0 || ref === void 0) return line;
1593
1602
  const comment = match.groups.comment ?? "";
1594
- const commentMatch = ACTION_PIN_COMMENT_RE.exec(comment);
1595
- const isCanonicalComment = commentMatch !== null && !LEGACY_COMMENT.test(comment);
1596
- if (ref === sha && isCanonicalComment) return line;
1603
+ const commentMatch = PIN_COMMENT_RE.exec(comment);
1604
+ if (ref === sha && isCanonicalComment(comment, version) && repoPath === CANONICAL_PATH) return line;
1597
1605
  if (from === void 0) {
1598
1606
  from = commentMatch ? commentMatch[1] : ref.slice(0, 7);
1599
1607
  }
1600
1608
  replaced += 1;
1601
- return `${indent2}${sha} # ${actionPinComment(version)}${eol}`;
1609
+ return `${indent2}${CANONICAL_PATH}@${sha} # v${version}${eol}`;
1602
1610
  });
1603
1611
  if (replaced === 0) {
1604
1612
  const hasAnyReference = lines.some((line) => ACTION_USES_LINE.test(line.endsWith("\r") ? line.slice(0, -1) : line));
@@ -54,7 +54,10 @@ import { defaultConfig } from "@svelte-vitals/core";
54
54
  import {
55
55
  ROBOTS_SOURCE_PATHS,
56
56
  SITEMAP_SOURCE_PATHS,
57
- findMinifyDisabled
57
+ SVELTE_CONFIG_FILES,
58
+ VITE_CONFIG_FILES,
59
+ findMinifyDisabled,
60
+ resolveKitPathsBase
58
61
  } from "@svelte-vitals/core";
59
62
  var ProjectError = class extends Error {
60
63
  constructor(message) {
@@ -143,14 +146,6 @@ async function robotsRefsSitemap(rt, cwd) {
143
146
  return void 0;
144
147
  }
145
148
  }
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
149
  async function detectViteMinifyDisabled(rt, cwd) {
155
150
  const exists = await Promise.all(VITE_CONFIG_FILES.map((f) => rt.exists(rt.join(cwd, f))));
156
151
  const file = VITE_CONFIG_FILES[exists.indexOf(true)];
@@ -162,12 +157,32 @@ async function detectViteMinifyDisabled(rt, cwd) {
162
157
  return void 0;
163
158
  }
164
159
  }
160
+ async function readFirstConfig(rt, cwd, files) {
161
+ for (const file of files) {
162
+ const path = rt.join(cwd, file);
163
+ if (!await rt.exists(path)) continue;
164
+ try {
165
+ return { file, source: await rt.readFile(path) };
166
+ } catch {
167
+ return void 0;
168
+ }
169
+ }
170
+ return void 0;
171
+ }
172
+ async function detectKitPathsBase(rt, cwd) {
173
+ const [viteConfig, svelteConfig] = await Promise.all([
174
+ readFirstConfig(rt, cwd, VITE_CONFIG_FILES),
175
+ readFirstConfig(rt, cwd, SVELTE_CONFIG_FILES)
176
+ ]);
177
+ return resolveKitPathsBase(viteConfig, svelteConfig);
178
+ }
165
179
  async function collectProjectFacts(rt, cwd) {
166
- const [hasRobotsTxt, hasSitemap, htmlLang, viteMinifyDisabled] = await Promise.all([
180
+ const [hasRobotsTxt, hasSitemap, htmlLang, viteMinifyDisabled, kitPathsBase] = await Promise.all([
167
181
  existsAny(rt, cwd, ROBOTS_SOURCE_PATHS),
168
182
  existsAny(rt, cwd, SITEMAP_SOURCE_PATHS),
169
183
  detectAppHtmlLang(rt, cwd),
170
- detectViteMinifyDisabled(rt, cwd)
184
+ detectViteMinifyDisabled(rt, cwd),
185
+ detectKitPathsBase(rt, cwd)
171
186
  ]);
172
187
  const robotsReferencesSitemap = await robotsRefsSitemap(rt, cwd);
173
188
  return {
@@ -175,7 +190,8 @@ async function collectProjectFacts(rt, cwd) {
175
190
  hasSitemap,
176
191
  htmlLang,
177
192
  ...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {},
178
- ...viteMinifyDisabled ? { viteMinifyDisabled } : {}
193
+ ...viteMinifyDisabled ? { viteMinifyDisabled } : {},
194
+ ...kitPathsBase ? { kitPathsBase } : {}
179
195
  };
180
196
  }
181
197
 
@@ -1167,16 +1183,27 @@ async function playMascotGreeting(opts) {
1167
1183
  import { existsSync as existsSync2 } from "fs";
1168
1184
  import { join as join5 } from "path";
1169
1185
  import { pathToFileURL } from "url";
1186
+ import {
1187
+ CATEGORIES,
1188
+ defaultConfig as defaultConfig2,
1189
+ resolveRuleOptions,
1190
+ shouldSkipRangeCheck,
1191
+ validateRuleSetting
1192
+ } from "@svelte-vitals/core";
1170
1193
 
1171
1194
  // src/rules-config.ts
1172
1195
  import { allRules } from "@svelte-vitals/core";
1173
1196
  var KNOWN_IDS = new Set(allRules.map((r) => r.id));
1197
+ var RULE_BY_ID = new Map(allRules.map((r) => [r.id, r]));
1174
1198
  function findUnknownRuleIds(ids) {
1175
1199
  return [...new Set(ids.filter((id) => !KNOWN_IDS.has(id)))];
1176
1200
  }
1177
1201
  function knownRuleIds() {
1178
1202
  return [...KNOWN_IDS].sort();
1179
1203
  }
1204
+ function ruleOptionsSpec(id) {
1205
+ return RULE_BY_ID.get(id)?.options;
1206
+ }
1180
1207
  function buildRulesConfig(allow, ignore) {
1181
1208
  const rules = {};
1182
1209
  if (allow.length > 0) {
@@ -1188,17 +1215,23 @@ function buildRulesConfig(allow, ignore) {
1188
1215
 
1189
1216
  // src/config-file.ts
1190
1217
  var CONFIG_FILENAMES = ["svelte-vitals.config.mjs", "svelte-vitals.config.js", "svelte-vitals.config.ts"];
1191
- var CATEGORIES = ["seo", "performance", "correctness", "security", "architecture"];
1192
1218
  var TREAT_DYNAMIC_AS_VALUES = ["pass", "warn", "fail"];
1193
1219
  var FAIL_ON_VALUES = ["critical", "warning", "info"];
1194
1220
  var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["treatDynamicAs", "metaComponents", "rules", "failOn", "weights", "overrides"]);
1195
- var RULE_SETTING_VALUES = ["off", "critical", "warning", "info"];
1196
1221
  function isPlainObject2(value) {
1197
1222
  return typeof value === "object" && value !== null && !Array.isArray(value);
1198
1223
  }
1199
1224
  function isMissingExtensionLoaderError(err) {
1200
1225
  return err instanceof Error && ("code" in err && err.code === "ERR_UNKNOWN_FILE_EXTENSION" || /Unknown file extension/.test(err.message));
1201
1226
  }
1227
+ function validateSetting(path, where, key, setting, allowOptions, baseline, skipRangeCheck) {
1228
+ const errors = validateRuleSetting(`${where}.${key}`, key, setting, ruleOptionsSpec(key), {
1229
+ allowOptions,
1230
+ ...baseline !== void 0 ? { baseline } : {},
1231
+ ...skipRangeCheck !== void 0 ? { skipRangeCheck } : {}
1232
+ });
1233
+ if (errors.length > 0) throw new Error(`${path}: ${errors.join(" ")}`);
1234
+ }
1202
1235
  function validateConfigFile(raw, path) {
1203
1236
  const warnings = [];
1204
1237
  const config = {};
@@ -1241,6 +1274,7 @@ function validateConfigFile(raw, path) {
1241
1274
  `${path}: unknown rule id(s) in rules: ${unknown.join(", ")}. Known rule ids: ${knownRuleIds().join(", ")}`
1242
1275
  );
1243
1276
  }
1277
+ for (const [key, setting] of Object.entries(rules)) validateSetting(path, "rules", key, setting, true);
1244
1278
  config.rules = rules;
1245
1279
  }
1246
1280
  if (raw.overrides !== void 0) {
@@ -1250,7 +1284,8 @@ function validateConfigFile(raw, path) {
1250
1284
  const isGlob = (v) => typeof v === "string" && v.length > 0;
1251
1285
  const isGlobs = (v) => isGlob(v) || Array.isArray(v) && v.length > 0 && v.every(isGlob);
1252
1286
  const overrides = [];
1253
- raw.overrides.forEach((entry, i) => {
1287
+ const rawOverrides = raw.overrides;
1288
+ rawOverrides.forEach((entry, i) => {
1254
1289
  if (!isPlainObject2(entry)) {
1255
1290
  throw new Error(`${path}: overrides[${i}] must be an object with 'route' and/or 'files', and 'rules'.`);
1256
1291
  }
@@ -1281,11 +1316,10 @@ function validateConfigFile(raw, path) {
1281
1316
  );
1282
1317
  }
1283
1318
  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
- }
1319
+ const isCategory = CATEGORIES.includes(key);
1320
+ const baseline = isCategory ? void 0 : resolveRuleOptions(key, ruleOptionsSpec(key), { ...defaultConfig2, rules: config.rules ?? {} });
1321
+ const skipRangeCheck = shouldSkipRangeCheck(rawOverrides, i, key, setting);
1322
+ validateSetting(path, `overrides[${i}].rules`, key, setting, !isCategory, baseline, skipRangeCheck);
1289
1323
  }
1290
1324
  overrides.push({
1291
1325
  ...entry.route !== void 0 ? { route: entry.route } : {},
@@ -1690,6 +1724,7 @@ export {
1690
1724
  isReporterName,
1691
1725
  findUnknownRuleIds,
1692
1726
  knownRuleIds,
1727
+ ruleOptionsSpec,
1693
1728
  buildRulesConfig,
1694
1729
  CONFIG_FILENAMES,
1695
1730
  loadConfigFile,
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
 
@@ -60,6 +60,8 @@ declare class ProjectError extends Error {
60
60
  declare function findUnknownRuleIds(ids: string[]): string[];
61
61
  /** All built-in rule ids, sorted — for help and error messages. */
62
62
  declare function knownRuleIds(): string[];
63
+ /** The options a rule declares, or undefined when it takes none. */
64
+ declare function ruleOptionsSpec(id: string): RuleOptionsSpec | undefined;
63
65
  /**
64
66
  * Build the per-rule config map from an allow-list (--rules) and a deny-list
65
67
  * (--ignore). An allow-list disables every rule not listed; deny always wins.
@@ -243,4 +245,4 @@ declare function applyScope(results: Result[], opts: ApplyScopeOptions): Promise
243
245
  */
244
246
  declare function run(opts?: RunOptions): Promise<number>;
245
247
 
246
- export { type AnalyzeOptions, type AnalyzeResult, type ApplyScopeOptions, type LoadedConfigFile, type ParseCache, ProjectError, type RunOptions, analyzeProject, applyScope, buildRulesConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, routeMatcher, run, spinnerEnabled };
248
+ 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-WSPRFLPP.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.32.0",
3
+ "version": "0.34.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",
@@ -44,17 +44,19 @@
44
44
  "magicast": "^0.5.3",
45
45
  "mri": "^1.2.0",
46
46
  "smol-toml": "^1.7.0",
47
- "svelte": "^5.56.6",
47
+ "svelte": "^5.56.8",
48
48
  "tinyglobby": "^0.2.17",
49
- "@svelte-vitals/core": "0.29.0"
49
+ "@svelte-vitals/core": "0.30.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/estree": "^1.0.9",
53
53
  "@types/node": "^24.13.3"
54
54
  },
55
55
  "scripts": {
56
- "build": "node scripts/gen-action-pin.mjs && tsup",
57
- "typecheck": "node scripts/gen-action-pin.mjs && tsc --noEmit",
58
- "test": "node scripts/gen-action-pin.mjs && vitest run"
56
+ "build": "tsup",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest run",
59
+ "gen:rules-index": "pnpm --filter @svelte-vitals/core build && node scripts/gen-rules-index.mjs",
60
+ "update-action-pin": "node scripts/gen-action-pin.mjs"
59
61
  }
60
62
  }