@svelte-vitals/core 0.12.0 → 0.13.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/index.d.ts CHANGED
@@ -21,6 +21,8 @@ interface Project {
21
21
  hasSitemap: boolean;
22
22
  /** <html lang> from app.html: presence 'own' when the attribute exists ('none' otherwise); value 'static' if non-empty, 'absent' if empty. */
23
23
  htmlLang: Detection;
24
+ /** Whether the static static/robots.txt references a sitemap (`Sitemap:` line). Undefined for a +server endpoint / absent / unreadable. */
25
+ robotsReferencesSitemap?: boolean;
24
26
  }
25
27
  declare const defaultProject: Project;
26
28
  /** A concrete, agent-actionable remediation for a finding (design §10, issue #18). */
@@ -111,6 +113,8 @@ interface HeadTag {
111
113
  hasAs?: boolean;
112
114
  /** True when a <link> has a `crossorigin` attribute (presence only; value is irrelevant to the checks). */
113
115
  hasCrossorigin?: boolean;
116
+ /** True when a <meta name="robots"> literal content contains `noindex`/`none`. Undefined when dynamic or absent. */
117
+ noindex?: boolean;
114
118
  /** Where this tag was set relative to the route. Never 'none' (absence = no tag). */
115
119
  presence: Exclude<Presence, 'none'>;
116
120
  /** Whether the tag's value is static/dynamic/absent (design §4). */
@@ -238,6 +242,13 @@ declare const perf002ImageLoading: Rule;
238
242
  declare const perf003PreloadAs: Rule;
239
243
  declare const perf004FontPreloadCrossorigin: Rule;
240
244
 
245
+ declare const seo010Indexability: Rule;
246
+ declare const seo011TwitterCard: Rule;
247
+ declare const seo012OgDescription: Rule;
248
+ declare const seo013OgUrl: Rule;
249
+ declare const seo014Viewport: Rule;
250
+ declare const seo015SitemapInRobots: Rule;
251
+
241
252
  declare const allRules: Rule[];
242
253
 
243
254
  interface RuleInfo {
@@ -265,6 +276,13 @@ interface HeadTagRuleOptions {
265
276
  rationale: string;
266
277
  /** Agent-actionable remediation attached to every finding (issue #18). */
267
278
  fix?: Fix;
279
+ /**
280
+ * When set, only heads for which this returns true are evaluated; others emit
281
+ * nothing. Use for tags whose canonical location is invisible to a given mode
282
+ * (e.g. viewport lives in app.html → only checkable in rendered mode), so the
283
+ * rule stays silent instead of false-flagging "missing".
284
+ */
285
+ appliesTo?: (head: ResolvedHead) => boolean;
268
286
  }
269
287
  /** Build a route-scope rule asserting the presence of a single head tag (design §11). */
270
288
  declare function headTagRule(opts: HeadTagRuleOptions): Rule;
@@ -437,4 +455,4 @@ declare function selectRules(rules: Rule[], config: Config): Rule[];
437
455
  /** Apply per-rule severity overrides to results (design §6). */
438
456
  declare function applyRuleSeverities(results: Result[], config: Config): Result[];
439
457
 
440
- export { BAND_COLOR, type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildHtmlDocument, buildJsonReport, classify, computeHealth, computeScore, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, escapeHtml, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, linkRule, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, runRules, safeHref, scoreBand, scoresByCategory, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
458
+ export { BAND_COLOR, type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildHtmlDocument, buildJsonReport, classify, computeHealth, computeScore, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, escapeHtml, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, linkRule, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, runRules, safeHref, scoreBand, scoresByCategory, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, summarize };
package/dist/index.js CHANGED
@@ -104,7 +104,8 @@ function headTagRule(opts) {
104
104
  rationale: opts.rationale,
105
105
  ...opts.fix ? { fix: opts.fix } : {},
106
106
  async check(ctx) {
107
- return ctx.heads.map((head) => {
107
+ const heads = opts.appliesTo ? ctx.heads.filter(opts.appliesTo) : ctx.heads;
108
+ return heads.map((head) => {
108
109
  const detection = detect(head, opts.match);
109
110
  const message = detection.presence === "none" ? `Missing ${opts.label}` : detection.value === "absent" ? `Empty ${opts.label}` : opts.label;
110
111
  return {
@@ -458,6 +459,134 @@ var perf004FontPreloadCrossorigin = linkRule({
458
459
  ok: (t) => t.hasCrossorigin === true
459
460
  });
460
461
 
462
+ // src/rules/seo/seo010-015.ts
463
+ var SEO010_FIX = {
464
+ description: 'If this route should be indexed, drop noindex from its <meta name="robots">.',
465
+ snippet: '<svelte:head>\n <meta name="robots" content="index, follow" />\n</svelte:head>',
466
+ lang: "svelte"
467
+ };
468
+ var seo010Indexability = {
469
+ id: "SEO010",
470
+ title: "Indexability",
471
+ category: "seo",
472
+ severity: "info",
473
+ scope: "route",
474
+ rationale: "A noindex directive removes the page from search results; an accidental noindex on a public route silently deindexes it.",
475
+ fix: SEO010_FIX,
476
+ async check(ctx) {
477
+ const docsUrl = docsUrlFor("SEO010");
478
+ const out = [];
479
+ for (const head of ctx.heads) {
480
+ const noindexed = head.tags.some((t) => t.kind === "meta" && t.name === "robots" && t.noindex === true);
481
+ if (!noindexed) continue;
482
+ out.push({
483
+ id: "SEO010",
484
+ category: "seo",
485
+ severity: "info",
486
+ detection: { presence: "none", value: "absent" },
487
+ // surfaced as an issue (isPenalized)
488
+ route: head.route,
489
+ location: head.file,
490
+ message: "Route is noindex \u2014 verify this is intentional",
491
+ recommendation: 'If this route should be indexed, remove noindex from its <meta name="robots">.',
492
+ docsUrl,
493
+ fix: { ...SEO010_FIX }
494
+ });
495
+ }
496
+ return out;
497
+ }
498
+ };
499
+ var seo011TwitterCard = headTagRule({
500
+ id: "SEO011",
501
+ title: "Twitter Card",
502
+ severity: "info",
503
+ match: (t) => t.kind === "meta" && t.name === "twitter:card",
504
+ label: '<meta name="twitter:card">',
505
+ recommendation: 'Add <meta name="twitter:card" content="summary_large_image"> so X/Twitter renders a rich card.',
506
+ rationale: "twitter:card selects how the page renders when shared on X/Twitter; without it the platform falls back to a basic link (Open Graph tags are used as fallbacks for the rest).",
507
+ fix: {
508
+ description: "Add a twitter:card meta tag in <svelte:head>.",
509
+ snippet: '<svelte:head>\n <meta name="twitter:card" content="summary_large_image" />\n</svelte:head>',
510
+ lang: "svelte"
511
+ }
512
+ });
513
+ var seo012OgDescription = headTagRule({
514
+ id: "SEO012",
515
+ title: "Open Graph description",
516
+ severity: "warning",
517
+ match: (t) => t.kind === "meta" && t.property === "og:description",
518
+ label: '<meta property="og:description">',
519
+ recommendation: 'Add <meta property="og:description">, or set openGraph.description on your meta component.',
520
+ rationale: "og:description is the summary shown under the title in social previews; without it platforms guess or show nothing, lowering click-through.",
521
+ fix: {
522
+ description: "Add an og:description meta tag in <svelte:head>.",
523
+ snippet: '<svelte:head>\n <meta property="og:description" content="A concise page summary." />\n</svelte:head>',
524
+ lang: "svelte"
525
+ }
526
+ });
527
+ var seo013OgUrl = headTagRule({
528
+ id: "SEO013",
529
+ title: "Open Graph URL",
530
+ severity: "info",
531
+ match: (t) => t.kind === "meta" && t.property === "og:url",
532
+ label: '<meta property="og:url">',
533
+ recommendation: 'Add <meta property="og:url"> with the canonical URL, or set openGraph.url on your meta component.',
534
+ rationale: "og:url tells social platforms the canonical address to attribute shares and likes to, consolidating engagement on one URL.",
535
+ fix: {
536
+ description: "Add an og:url meta tag in <svelte:head>.",
537
+ snippet: '<svelte:head>\n <meta property="og:url" content="https://example.com/this-page" />\n</svelte:head>',
538
+ lang: "svelte"
539
+ }
540
+ });
541
+ var seo014Viewport = headTagRule({
542
+ id: "SEO014",
543
+ title: "Viewport",
544
+ severity: "warning",
545
+ match: (t) => t.kind === "meta" && t.name === "viewport",
546
+ label: '<meta name="viewport">',
547
+ // Viewport canonically lives in app.html, which static (CLI) mode does not
548
+ // resolve into head tags — only evaluate rendered heads so the rule stays
549
+ // silent there instead of false-flagging "missing" on every route.
550
+ appliesTo: (head) => head.source === "rendered",
551
+ recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1"> (usually in app.html).',
552
+ rationale: "Without a viewport meta tag the page is not mobile-responsive, which Google penalizes under mobile-first indexing.",
553
+ fix: {
554
+ description: "Add the viewport meta tag (typically in src/app.html <head>).",
555
+ snippet: '<meta name="viewport" content="width=device-width, initial-scale=1" />',
556
+ lang: "html"
557
+ }
558
+ });
559
+ var SEO015_FIX = {
560
+ description: "Add a Sitemap: line to static/robots.txt.",
561
+ snippet: "User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml",
562
+ lang: "text"
563
+ };
564
+ var seo015SitemapInRobots = {
565
+ id: "SEO015",
566
+ title: "Sitemap referenced in robots.txt",
567
+ category: "seo",
568
+ severity: "info",
569
+ scope: "project",
570
+ rationale: "A Sitemap: line in robots.txt helps crawlers discover your sitemap; without it discovery relies on manual submission.",
571
+ fix: SEO015_FIX,
572
+ async check(ctx) {
573
+ const { hasRobotsTxt, hasSitemap, robotsReferencesSitemap } = ctx.project;
574
+ if (!(hasRobotsTxt && hasSitemap && robotsReferencesSitemap === false)) return [];
575
+ return [
576
+ {
577
+ id: "SEO015",
578
+ category: "seo",
579
+ severity: "info",
580
+ detection: { presence: "none", value: "absent" },
581
+ message: "robots.txt does not reference your sitemap",
582
+ recommendation: "Add a Sitemap: line to static/robots.txt pointing at your sitemap.xml.",
583
+ docsUrl: docsUrlFor("SEO015"),
584
+ fix: { ...SEO015_FIX }
585
+ }
586
+ ];
587
+ }
588
+ };
589
+
461
590
  // src/rules/index.ts
462
591
  var allRules = [
463
592
  seo001Title,
@@ -472,7 +601,13 @@ var allRules = [
472
601
  perf001ImageDimensions,
473
602
  perf002ImageLoading,
474
603
  perf003PreloadAs,
475
- perf004FontPreloadCrossorigin
604
+ perf004FontPreloadCrossorigin,
605
+ seo010Indexability,
606
+ seo011TwitterCard,
607
+ seo012OgDescription,
608
+ seo013OgUrl,
609
+ seo014Viewport,
610
+ seo015SitemapInRobots
476
611
  ];
477
612
  function explainRule(id) {
478
613
  const target = id.toUpperCase();
@@ -1099,5 +1234,11 @@ export {
1099
1234
  seo007Sitemap,
1100
1235
  seo008JsonLd,
1101
1236
  seo009HtmlLang,
1237
+ seo010Indexability,
1238
+ seo011TwitterCard,
1239
+ seo012OgDescription,
1240
+ seo013OgUrl,
1241
+ seo014Viewport,
1242
+ seo015SitemapInRobots,
1102
1243
  summarize
1103
1244
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",