@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 +19 -1
- package/dist/index.js +143 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
};
|