svelte-vitals 0.30.0 → 0.31.1

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/README.md CHANGED
@@ -33,16 +33,16 @@ Architecture Score: 100/100 (route avg 100)
33
33
 
34
34
  Critical (2)
35
35
  ────────────────────────
36
- SEO001 Missing <title>
36
+ seo/title-presence Missing <title>
37
37
  /
38
38
  src/routes/+page.svelte
39
- SEO002 Missing <meta name="description">
39
+ seo/description-presence Missing <meta name="description">
40
40
  /
41
41
  src/routes/+page.svelte
42
42
 
43
43
  Warnings (10)
44
44
  ────────────────────────
45
- SEO003 Missing <link rel="canonical">
45
+ seo/canonical-url Missing <link rel="canonical">
46
46
  /
47
47
  src/routes/+page.svelte
48
48
  …and 1 more
package/dist/bin.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  readCoreVersion,
10
10
  readPackageVersion,
11
11
  run
12
- } from "./chunk-QBXO46PU.js";
12
+ } from "./chunk-3YO2A3YF.js";
13
13
 
14
14
  // src/bin.ts
15
15
  import mri3 from "mri";
@@ -341,6 +341,12 @@ 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
+
344
350
  // src/ci/workflow.ts
345
351
  var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
346
352
  var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
@@ -371,7 +377,7 @@ function buildWorkflowYaml(opts) {
371
377
  ` - uses: actions/checkout@${CHECKOUT_SHA} # ${CHECKOUT_VERSION}`,
372
378
  " with:",
373
379
  " fetch-depth: 0",
374
- ` - uses: oekazuma/svelte-vitals/packages/action@${actionSha} # @svelte-vitals/action@${actionVersion}`,
380
+ ` - uses: oekazuma/svelte-vitals/packages/action@${actionSha} # ${actionPinComment(actionVersion)}`,
375
381
  " with:",
376
382
  " diff: origin/${{ github.base_ref }}",
377
383
  " baseline: origin/${{ github.base_ref }}",
@@ -505,7 +511,7 @@ without a judgment call of its own.
505
511
  none today, by design), no code edits, no commits, no formatters, no
506
512
  dependency installs. Run svelte-vitals read-only, for evidence only.
507
513
  3. **Plans must be fully self-contained.** The executor has zero context
508
- from this conversation. Never write "fix it like SEO001 above" \u2014 inline
514
+ from this conversation. Never write "fix it like seo/title-presence above" \u2014 inline
509
515
  the exact file, line, current code, and the exact fix (svelte-vitals'
510
516
  \`fix.snippet\`/\`fix.description\` for the rule, quoted verbatim \u2014 see
511
517
  below).
@@ -789,7 +795,7 @@ function buildConfigFileTemplate(opts = {}) {
789
795
  const header = "// svelte-vitals config file \u2014 https://oekazuma.github.io/svelte-vitals/guides/configuration/\n";
790
796
  const options = ` // treatDynamicAs: 'pass', // 'pass' | 'warn' | 'fail' \u2014 how {data.title}-style dynamic values are scored
791
797
  // metaComponents: ['Seo'], // component names that resolve SEO tags into <head>
792
- // rules: {}, // e.g. { SEO001: 'off' } to disable a rule
798
+ // rules: {}, // e.g. { 'seo/title-presence': 'off' } to disable a rule
793
799
  // failOn: 'critical', // 'critical' | 'warning' | 'info'
794
800
  // weights: {} // e.g. { seo: 2 } \u2014 per-category weight for the combined Health score`;
795
801
  if (opts.useDefineConfig) {
@@ -998,8 +1004,8 @@ function readInstalledViteVersion(io) {
998
1004
  }
999
1005
 
1000
1006
  // src/ci/action-pin.generated.ts
1001
- var ACTION_SHA = "81d49a11b71a16ea1294212d43520a0137b4bd5e";
1002
- var ACTION_VERSION = "0.3.5";
1007
+ var ACTION_SHA = "7ae39edda8bfbaba16ddb6c24871619a9ebe0a5d";
1008
+ var ACTION_VERSION = "0.3.7";
1003
1009
 
1004
1010
  // src/install/index.ts
1005
1011
  function detectPackageManagerNear(io, appDir) {
@@ -1572,6 +1578,7 @@ import mri2 from "mri";
1572
1578
 
1573
1579
  // src/ci/upgrade.ts
1574
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@/;
1575
1582
  function upgradeActionPin(content, sha, version) {
1576
1583
  const lines = content.split("\n");
1577
1584
  let replaced = 0;
@@ -1583,13 +1590,15 @@ function upgradeActionPin(content, sha, version) {
1583
1590
  if (!match || !match.groups) return line;
1584
1591
  const { indent: indent2, ref } = match.groups;
1585
1592
  if (indent2 === void 0 || ref === void 0) return line;
1586
- if (ref === sha) return line;
1593
+ 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;
1587
1597
  if (from === void 0) {
1588
- const commentMatch = /#\s*@svelte-vitals\/action@(\S+)/.exec(match.groups.comment ?? "");
1589
1598
  from = commentMatch ? commentMatch[1] : ref.slice(0, 7);
1590
1599
  }
1591
1600
  replaced += 1;
1592
- return `${indent2}${sha} # @svelte-vitals/action@${version}${eol}`;
1601
+ return `${indent2}${sha} # ${actionPinComment(version)}${eol}`;
1593
1602
  });
1594
1603
  if (replaced === 0) {
1595
1604
  const hasAnyReference = lines.some((line) => ACTION_USES_LINE.test(line.endsWith("\r") ? line.slice(0, -1) : line));
@@ -53,7 +53,8 @@ import { defaultConfig } from "@svelte-vitals/core";
53
53
  // src/providers/source/project.ts
54
54
  import {
55
55
  ROBOTS_SOURCE_PATHS,
56
- SITEMAP_SOURCE_PATHS
56
+ SITEMAP_SOURCE_PATHS,
57
+ findMinifyDisabled
57
58
  } from "@svelte-vitals/core";
58
59
  var ProjectError = class extends Error {
59
60
  constructor(message) {
@@ -110,18 +111,39 @@ async function robotsRefsSitemap(rt, cwd) {
110
111
  return void 0;
111
112
  }
112
113
  }
114
+ var VITE_CONFIG_FILES = [
115
+ "vite.config.js",
116
+ "vite.config.mjs",
117
+ "vite.config.ts",
118
+ "vite.config.cjs",
119
+ "vite.config.mts",
120
+ "vite.config.cts"
121
+ ];
122
+ async function detectViteMinifyDisabled(rt, cwd) {
123
+ const exists = await Promise.all(VITE_CONFIG_FILES.map((f) => rt.exists(rt.join(cwd, f))));
124
+ const file = VITE_CONFIG_FILES[exists.indexOf(true)];
125
+ if (!file) return void 0;
126
+ try {
127
+ const hit = findMinifyDisabled(await rt.readFile(rt.join(cwd, file)));
128
+ return hit ? { file, line: hit.line } : void 0;
129
+ } catch {
130
+ return void 0;
131
+ }
132
+ }
113
133
  async function collectProjectFacts(rt, cwd) {
114
- const [hasRobotsTxt, hasSitemap, htmlLang] = await Promise.all([
134
+ const [hasRobotsTxt, hasSitemap, htmlLang, viteMinifyDisabled] = await Promise.all([
115
135
  existsAny(rt, cwd, ROBOTS_SOURCE_PATHS),
116
136
  existsAny(rt, cwd, SITEMAP_SOURCE_PATHS),
117
- detectAppHtmlLang(rt, cwd)
137
+ detectAppHtmlLang(rt, cwd),
138
+ detectViteMinifyDisabled(rt, cwd)
118
139
  ]);
119
140
  const robotsReferencesSitemap = await robotsRefsSitemap(rt, cwd);
120
141
  return {
121
142
  hasRobotsTxt,
122
143
  hasSitemap,
123
144
  htmlLang,
124
- ...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {}
145
+ ...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {},
146
+ ...viteMinifyDisabled ? { viteMinifyDisabled } : {}
125
147
  };
126
148
  }
127
149
 
@@ -139,16 +161,16 @@ function exprValue(node) {
139
161
  }
140
162
  function resolveMetaObject(attr, keyMap) {
141
163
  if (!attr) return { tags: [], opaque: false };
142
- const expr = attr.value?.type === "ExpressionTag" ? attr.value.expression : void 0;
164
+ const expr = attr.value !== true && !Array.isArray(attr.value) ? attr.value.expression : void 0;
143
165
  if (!expr || expr.type !== "ObjectExpression") return { tags: [], opaque: true };
144
166
  const tags = [];
145
167
  let opaque = false;
146
- for (const prop of expr.properties ?? []) {
147
- if (prop?.type !== "Property" || prop.computed) {
168
+ for (const prop of expr.properties) {
169
+ if (prop.type !== "Property" || prop.computed) {
148
170
  opaque = true;
149
171
  continue;
150
172
  }
151
- const key = prop.key?.name ?? prop.key?.value;
173
+ const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? prop.key.value : void 0;
152
174
  const make = typeof key === "string" ? keyMap[key] : void 0;
153
175
  if (make) tags.push(make(exprValue(prop.value)));
154
176
  }
@@ -168,7 +190,7 @@ var TWITTER_KEYS = {
168
190
 
169
191
  // src/providers/source/adapters/svelte-meta-tags.ts
170
192
  function findAttr(attributes, name) {
171
- return attributes.find((a) => a?.type === "Attribute" && a.name === name);
193
+ return attributes.find((a) => a.type === "Attribute" && a.name === name);
172
194
  }
173
195
  var svelteMetaTagsAdapter = {
174
196
  match(info) {
@@ -221,7 +243,7 @@ var svelteMetaTagsJsonLdAdapter = {
221
243
  // src/providers/source/adapters/svelte-seo.ts
222
244
  import { attrValueOf as attrValueOf2, attrTextOf as attrTextOf2 } from "@svelte-vitals/core";
223
245
  function findAttr2(attributes, name) {
224
- return attributes.find((a) => a?.type === "Attribute" && a.name === name);
246
+ return attributes.find((a) => a.type === "Attribute" && a.name === name);
225
247
  }
226
248
  var svelteSeoAdapter = {
227
249
  match(info) {
@@ -273,28 +295,31 @@ import {
273
295
  // src/providers/source/imports.ts
274
296
  function addImportsFromProgram(program, map) {
275
297
  for (const node of program?.body ?? []) {
276
- if (node?.type !== "ImportDeclaration") continue;
277
- const source = String(node.source?.value ?? "");
278
- for (const spec of node.specifiers ?? []) {
279
- const local = spec?.local?.name;
298
+ if (node.type !== "ImportDeclaration") continue;
299
+ const source = String(node.source.value ?? "");
300
+ for (const spec of node.specifiers) {
301
+ const local = spec.local?.name;
280
302
  if (!local) continue;
281
303
  if (spec.type === "ImportDefaultSpecifier") {
282
304
  map.set(local, { source, imported: "default" });
283
305
  } else if (spec.type === "ImportSpecifier") {
284
- map.set(local, { source, imported: spec.imported?.name ?? spec.imported?.value ?? local });
306
+ const imported = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value;
307
+ map.set(local, { source, imported: typeof imported === "string" ? imported : local });
285
308
  }
286
309
  }
287
310
  }
288
311
  }
289
312
  function collectImports(ast) {
290
- const root = ast;
291
313
  const map = /* @__PURE__ */ new Map();
292
- addImportsFromProgram(root?.instance?.content, map);
293
- addImportsFromProgram(root?.module?.content, map);
314
+ addImportsFromProgram(ast.instance?.content, map);
315
+ addImportsFromProgram(ast.module?.content, map);
294
316
  return map;
295
317
  }
296
318
 
297
319
  // src/providers/source/parse.ts
320
+ function childOf(node, key) {
321
+ return node[key];
322
+ }
298
323
  function collectSvelteHeads(node, acc) {
299
324
  if (Array.isArray(node)) {
300
325
  for (const child of node) collectSvelteHeads(child, acc);
@@ -303,32 +328,33 @@ function collectSvelteHeads(node, acc) {
303
328
  if (!node || typeof node !== "object") return;
304
329
  if (node.type === "SvelteHead") acc.push(node);
305
330
  for (const key of CHILD_NODE_KEYS) {
306
- if (key in node) collectSvelteHeads(node[key], acc);
331
+ if (key in node) collectSvelteHeads(childOf(node, key), acc);
307
332
  }
308
333
  }
309
334
  function tagsFromHead(head) {
310
335
  const tags = [];
311
- const children = head?.fragment?.nodes ?? [];
336
+ const children = head.fragment.nodes;
312
337
  for (const node of children) {
313
- if (node?.type === "TitleElement") {
314
- const titleNodes = node.fragment?.nodes ?? [];
338
+ if (node.type === "TitleElement") {
339
+ const titleNodes = node.fragment.nodes;
315
340
  const text = textFromNodes(titleNodes);
316
341
  tags.push({ kind: "title", value: valueFromNodes(titleNodes), ...text !== void 0 ? { text } : {} });
317
342
  continue;
318
343
  }
319
- if (node?.type !== "RegularElement") continue;
344
+ if (node.type !== "RegularElement") continue;
345
+ const attributes = node.attributes;
320
346
  if (node.name === "meta") {
321
- const charset = attrValue(node.attributes, "charset");
347
+ const charset = attrValue(attributes, "charset");
322
348
  if (charset !== "absent") {
323
349
  tags.push({ kind: "meta", name: "charset", value: charset });
324
350
  continue;
325
351
  }
326
- const name = attrText(node.attributes, "name");
327
- const property = attrText(node.attributes, "property");
328
- const content = name === "robots" ? attrText(node.attributes, "content") : void 0;
352
+ const name = attrText(attributes, "name");
353
+ const property = attrText(attributes, "property");
354
+ const content = name === "robots" ? attrText(attributes, "content") : void 0;
329
355
  const noindex = content !== void 0 && /(^|[\s,])(noindex|none)([\s,]|$)/i.test(content);
330
- const contentValue = attrValue(node.attributes, "content");
331
- const descText = name === "description" && contentValue === "static" ? attrText(node.attributes, "content") : void 0;
356
+ const contentValue = attrValue(attributes, "content");
357
+ const descText = name === "description" && contentValue === "static" ? attrText(attributes, "content") : void 0;
332
358
  tags.push({
333
359
  kind: "meta",
334
360
  ...name ? { name } : {},
@@ -338,33 +364,33 @@ function tagsFromHead(head) {
338
364
  ...descText !== void 0 ? { text: descText } : {}
339
365
  });
340
366
  } else if (node.name === "link") {
341
- const rel = attrText(node.attributes, "rel");
342
- const hasAs = findAttr3(node.attributes, "as") !== void 0;
343
- const asLiteral = attrText(node.attributes, "as");
344
- const hasCrossorigin = findAttr3(node.attributes, "crossorigin") !== void 0;
345
- const hreflang = attrText(node.attributes, "hreflang");
346
- const href = attrText(node.attributes, "href");
367
+ const rel = attrText(attributes, "rel");
368
+ const hasAs = findAttr3(attributes, "as") !== void 0;
369
+ const asLiteral = attrText(attributes, "as");
370
+ const hasCrossorigin = findAttr3(attributes, "crossorigin") !== void 0;
371
+ const hreflang = attrText(attributes, "hreflang");
372
+ const href = attrText(attributes, "href");
347
373
  tags.push({
348
374
  kind: "link",
349
375
  ...rel ? { rel } : {},
350
- value: attrValue(node.attributes, "href"),
376
+ value: attrValue(attributes, "href"),
351
377
  ...hasAs ? { hasAs: true } : {},
352
378
  ...asLiteral ? { as: asLiteral } : {},
353
379
  ...hasCrossorigin ? { hasCrossorigin: true } : {},
354
- // Keep a literal empty hreflang="" (present-but-invalid) so SEO026 can flag it.
380
+ // Keep a literal empty hreflang="" (present-but-invalid) so seo/hreflang can flag it.
355
381
  ...hreflang !== void 0 ? { hreflang } : {},
356
382
  ...href ? { href } : {}
357
383
  });
358
384
  } else if (node.name === "script") {
359
- const type = attrText(node.attributes, "type");
385
+ const type = attrText(attributes, "type");
360
386
  if (type === "application/ld+json") {
361
- const nodes = node.fragment?.nodes ?? [];
387
+ const nodes = node.fragment.nodes;
362
388
  const raw = textFromNodes(nodes);
363
389
  tags.push({ kind: "jsonld", value: valueFromNodes(nodes), ...raw !== void 0 ? { jsonld: raw } : {} });
364
390
  } else {
365
- const src = attrText(node.attributes, "src");
391
+ const src = attrText(attributes, "src");
366
392
  if (src) {
367
- const blocking = findAttr3(node.attributes, "defer") === void 0 && findAttr3(node.attributes, "async") === void 0 && type !== "module";
393
+ const blocking = findAttr3(attributes, "defer") === void 0 && findAttr3(attributes, "async") === void 0 && type !== "module";
368
394
  tags.push({ kind: "script", value: "static", href: src, ...blocking ? { blocking: true } : {} });
369
395
  }
370
396
  }
@@ -378,16 +404,16 @@ function collectComponents(node, acc) {
378
404
  return;
379
405
  }
380
406
  if (!node || typeof node !== "object") return;
381
- if (node.type === "Component" && typeof node.name === "string") {
382
- const attributes = node.attributes ?? [];
407
+ if (node.type === "Component") {
408
+ const attributes = node.attributes;
383
409
  acc.push({
384
410
  name: node.name,
385
411
  attributes,
386
- hasSpread: attributes.some((a) => a?.type === "SpreadAttribute")
412
+ hasSpread: attributes.some((a) => a.type === "SpreadAttribute")
387
413
  });
388
414
  }
389
415
  for (const key of CHILD_NODE_KEYS) {
390
- if (key in node) collectComponents(node[key], acc);
416
+ if (key in node) collectComponents(childOf(node, key), acc);
391
417
  }
392
418
  }
393
419
  function collectImages(node, source, acc) {
@@ -397,8 +423,8 @@ function collectImages(node, source, acc) {
397
423
  }
398
424
  if (!node || typeof node !== "object") return;
399
425
  if (node.type === "RegularElement" && node.name === "img") {
400
- const attrs = node.attributes ?? [];
401
- const hasSpread = attrs.some((a) => a?.type === "SpreadAttribute");
426
+ const attrs = node.attributes;
427
+ const hasSpread = node.attributes.some((a) => a.type === "SpreadAttribute");
402
428
  acc.push({
403
429
  hasWidth: hasSpread || Boolean(findAttr3(attrs, "width")),
404
430
  hasHeight: hasSpread || Boolean(findAttr3(attrs, "height")),
@@ -411,7 +437,7 @@ function collectImages(node, source, acc) {
411
437
  });
412
438
  }
413
439
  for (const key of CHILD_NODE_KEYS) {
414
- if (key in node) collectImages(node[key], source, acc);
440
+ if (key in node) collectImages(childOf(node, key), source, acc);
415
441
  }
416
442
  }
417
443
  function collectHeadings(node, source, acc) {
@@ -421,23 +447,23 @@ function collectHeadings(node, source, acc) {
421
447
  }
422
448
  if (!node || typeof node !== "object") return;
423
449
  if (node.type === "SvelteHead") return;
424
- if (node.type === "RegularElement" && typeof node.name === "string" && /^h[1-6]$/.test(node.name)) {
450
+ if (node.type === "RegularElement" && /^h[1-6]$/.test(node.name)) {
425
451
  acc.push({ level: Number(node.name[1]), line: lineOf(source, node.start) });
426
452
  }
427
453
  for (const key of CHILD_NODE_KEYS) {
428
- if (key in node) collectHeadings(node[key], source, acc);
454
+ if (key in node) collectHeadings(childOf(node, key), source, acc);
429
455
  }
430
456
  }
431
457
  function parseFile(source, filename) {
432
458
  const ast = parse(source, { modern: true, filename });
433
459
  const heads = [];
434
- collectSvelteHeads(ast.fragment ?? ast, heads);
460
+ collectSvelteHeads(ast.fragment, heads);
435
461
  const components = [];
436
- collectComponents(ast.fragment ?? ast, components);
462
+ collectComponents(ast.fragment, components);
437
463
  const images = [];
438
- collectImages(ast.fragment ?? ast, source, images);
464
+ collectImages(ast.fragment, source, images);
439
465
  const headings = [];
440
- collectHeadings(ast.fragment ?? ast, source, headings);
466
+ collectHeadings(ast.fragment, source, headings);
441
467
  return {
442
468
  headTags: heads.flatMap(tagsFromHead),
443
469
  components,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { HeadTag, RuleSetting, Config, Severity, Category, Result } from '@svelte-vitals/core';
2
2
  export { defineConfig } from '@svelte-vitals/core';
3
+ import { AST } from 'svelte/compiler';
3
4
 
4
5
  /** A resolved import binding: which module, and which export ('default' for default imports). */
5
6
  interface ImportInfo {
@@ -11,10 +12,9 @@ type ImportMap = Map<string, ImportInfo>;
11
12
 
12
13
  /** A head tag parsed from one file, before layout-chain presence is assigned. */
13
14
  type ParsedTag = Omit<HeadTag, 'presence' | 'file'>;
14
- type Node = any;
15
15
  interface ComponentUse {
16
16
  name: string;
17
- attributes: Node[];
17
+ attributes: AST.Component['attributes'];
18
18
  hasSpread: boolean;
19
19
  }
20
20
  interface ParsedImage {
@@ -27,7 +27,7 @@ interface ParsedImage {
27
27
  /** 1-based source line, or 0 if unknown. */
28
28
  line: number;
29
29
  }
30
- /** A page-body heading (<h1>–<h6>) parsed from one file (SEO027). */
30
+ /** A page-body heading (<h1>–<h6>) parsed from one file (seo/single-h1). */
31
31
  interface ParsedHeading {
32
32
  /** Heading level 1–6. */
33
33
  level: number;
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  routeMatcher,
11
11
  run,
12
12
  spinnerEnabled
13
- } from "./chunk-QBXO46PU.js";
13
+ } from "./chunk-3YO2A3YF.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.30.0",
3
+ "version": "0.31.1",
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,11 +44,12 @@
44
44
  "magicast": "^0.5.3",
45
45
  "mri": "^1.2.0",
46
46
  "smol-toml": "^1.7.0",
47
- "svelte": "^5.56.4",
47
+ "svelte": "^5.56.6",
48
48
  "tinyglobby": "^0.2.17",
49
- "@svelte-vitals/core": "0.27.0"
49
+ "@svelte-vitals/core": "0.28.0"
50
50
  },
51
51
  "devDependencies": {
52
+ "@types/estree": "^1.0.9",
52
53
  "@types/node": "^24.13.3"
53
54
  },
54
55
  "scripts": {