@svelte-vitals/core 0.3.0 → 0.4.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 +17 -1
- package/dist/index.js +110 -11
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,15 @@ interface Project {
|
|
|
23
23
|
htmlLang: Detection;
|
|
24
24
|
}
|
|
25
25
|
declare const defaultProject: Project;
|
|
26
|
+
/** A concrete, agent-actionable remediation for a finding (design §10, issue #18). */
|
|
27
|
+
interface Fix {
|
|
28
|
+
/** One-line imperative instruction, e.g. 'Add a <meta name="description"> inside <svelte:head>.' */
|
|
29
|
+
description: string;
|
|
30
|
+
/** Concrete code to insert or a file's contents to create. */
|
|
31
|
+
snippet?: string;
|
|
32
|
+
/** Markdown fenced-code language for `snippet` (default 'svelte'). */
|
|
33
|
+
lang?: string;
|
|
34
|
+
}
|
|
26
35
|
/** A single rule finding for one route (or the whole project). */
|
|
27
36
|
interface Result {
|
|
28
37
|
/** Rule id, e.g. 'SEO001'. */
|
|
@@ -36,6 +45,8 @@ interface Result {
|
|
|
36
45
|
message: string;
|
|
37
46
|
recommendation?: string;
|
|
38
47
|
docsUrl?: string;
|
|
48
|
+
/** Agent-actionable remediation (issue #18). */
|
|
49
|
+
fix?: Fix;
|
|
39
50
|
}
|
|
40
51
|
type Scope = 'route' | 'project';
|
|
41
52
|
type Category = 'seo' | 'performance' | 'a11y' | 'maintainability';
|
|
@@ -192,6 +203,8 @@ interface HeadTagRuleOptions {
|
|
|
192
203
|
/** Short human label, e.g. 'description'. */
|
|
193
204
|
label: string;
|
|
194
205
|
recommendation: string;
|
|
206
|
+
/** Agent-actionable remediation attached to every finding (issue #18). */
|
|
207
|
+
fix?: Fix;
|
|
195
208
|
}
|
|
196
209
|
/** Build a route-scope rule asserting the presence of a single head tag (design §11). */
|
|
197
210
|
declare function headTagRule(opts: HeadTagRuleOptions): Rule;
|
|
@@ -231,6 +244,9 @@ declare function formatJsonReport(results: Result[], config: Config, meta: {
|
|
|
231
244
|
version: string;
|
|
232
245
|
}): string;
|
|
233
246
|
|
|
247
|
+
/** Render failing findings as an agent-actionable Markdown remediation document (issue #18). */
|
|
248
|
+
declare function formatAgentReport(results: Result[], config: Config): string;
|
|
249
|
+
|
|
234
250
|
/** Drop rules disabled via config (design §6). */
|
|
235
251
|
declare function selectRules(rules: Rule[], config: Config): Rule[];
|
|
236
252
|
/** Apply per-rule severity overrides to results (design §6). */
|
|
@@ -252,4 +268,4 @@ interface ScoreOptions {
|
|
|
252
268
|
/** Compute the headline score and its breakdown (design §12). */
|
|
253
269
|
declare function computeScore(results: Result[], config: Config, options?: ScoreOptions): ScoreResult;
|
|
254
270
|
|
|
255
|
-
export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type HeadProvider, type HeadTag, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type Result, type Rule, type RuleContext, 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, classify, computeScore, defaultConfig, defaultProject, defineConfig, effectiveSeverity, formatConsoleReport, formatJsonReport, hasFailureAtOrAbove, headTagRule, isPenalized, runRules, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
|
|
271
|
+
export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type Result, type Rule, type RuleContext, 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, classify, computeScore, defaultConfig, defaultProject, defineConfig, effectiveSeverity, formatAgentReport, formatConsoleReport, formatJsonReport, hasFailureAtOrAbove, headTagRule, isPenalized, runRules, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
|
package/dist/index.js
CHANGED
|
@@ -71,7 +71,12 @@ var seo001Title = {
|
|
|
71
71
|
location: head.file,
|
|
72
72
|
message: messageFor(detection),
|
|
73
73
|
recommendation: "Add a <title> inside <svelte:head>, e.g. <title>{data.title}</title>, or set it via your meta component.",
|
|
74
|
-
docsUrl: DOCS_URL
|
|
74
|
+
docsUrl: DOCS_URL,
|
|
75
|
+
fix: {
|
|
76
|
+
description: "Add a <title> inside <svelte:head> (a dynamic title is fine).",
|
|
77
|
+
snippet: "<svelte:head>\n <title>{data.title}</title>\n</svelte:head>",
|
|
78
|
+
lang: "svelte"
|
|
79
|
+
}
|
|
75
80
|
};
|
|
76
81
|
});
|
|
77
82
|
}
|
|
@@ -102,7 +107,10 @@ function headTagRule(opts) {
|
|
|
102
107
|
location: head.file,
|
|
103
108
|
message,
|
|
104
109
|
recommendation: opts.recommendation,
|
|
105
|
-
docsUrl
|
|
110
|
+
docsUrl,
|
|
111
|
+
// Copy per finding: opts.fix is a rule-level template shared across all
|
|
112
|
+
// results this rule emits; a fresh object keeps findings independent.
|
|
113
|
+
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
106
114
|
};
|
|
107
115
|
});
|
|
108
116
|
}
|
|
@@ -116,7 +124,12 @@ var seo002Description = headTagRule({
|
|
|
116
124
|
severity: "critical",
|
|
117
125
|
match: (t) => t.kind === "meta" && t.name === "description",
|
|
118
126
|
label: '<meta name="description">',
|
|
119
|
-
recommendation: 'Add a <meta name="description"> in <svelte:head>, or set the description on your meta component.'
|
|
127
|
+
recommendation: 'Add a <meta name="description"> in <svelte:head>, or set the description on your meta component.',
|
|
128
|
+
fix: {
|
|
129
|
+
description: 'Add a <meta name="description"> inside <svelte:head>, or set description on your meta component.',
|
|
130
|
+
snippet: '<svelte:head>\n <meta name="description" content="A concise page summary." />\n</svelte:head>',
|
|
131
|
+
lang: "svelte"
|
|
132
|
+
}
|
|
120
133
|
});
|
|
121
134
|
var seo003Canonical = headTagRule({
|
|
122
135
|
id: "SEO003",
|
|
@@ -124,7 +137,12 @@ var seo003Canonical = headTagRule({
|
|
|
124
137
|
severity: "warning",
|
|
125
138
|
match: (t) => t.kind === "link" && t.rel === "canonical",
|
|
126
139
|
label: '<link rel="canonical">',
|
|
127
|
-
recommendation: 'Add <link rel="canonical"> in <svelte:head>, or set the canonical prop on your meta component.'
|
|
140
|
+
recommendation: 'Add <link rel="canonical"> in <svelte:head>, or set the canonical prop on your meta component.',
|
|
141
|
+
fix: {
|
|
142
|
+
description: 'Add <link rel="canonical"> inside <svelte:head>, or set the canonical prop on your meta component.',
|
|
143
|
+
snippet: '<svelte:head>\n <link rel="canonical" href="https://example.com/this-page" />\n</svelte:head>',
|
|
144
|
+
lang: "svelte"
|
|
145
|
+
}
|
|
128
146
|
});
|
|
129
147
|
var seo004OgImage = headTagRule({
|
|
130
148
|
id: "SEO004",
|
|
@@ -132,7 +150,12 @@ var seo004OgImage = headTagRule({
|
|
|
132
150
|
severity: "warning",
|
|
133
151
|
match: (t) => t.kind === "meta" && t.property === "og:image",
|
|
134
152
|
label: '<meta property="og:image">',
|
|
135
|
-
recommendation: 'Add <meta property="og:image">, or set openGraph.images on your meta component.'
|
|
153
|
+
recommendation: 'Add <meta property="og:image">, or set openGraph.images on your meta component.',
|
|
154
|
+
fix: {
|
|
155
|
+
description: 'Add <meta property="og:image">, or set openGraph.images on your meta component.',
|
|
156
|
+
snippet: '<svelte:head>\n <meta property="og:image" content="https://example.com/og.png" />\n</svelte:head>',
|
|
157
|
+
lang: "svelte"
|
|
158
|
+
}
|
|
136
159
|
});
|
|
137
160
|
var seo005OgTitle = headTagRule({
|
|
138
161
|
id: "SEO005",
|
|
@@ -140,7 +163,12 @@ var seo005OgTitle = headTagRule({
|
|
|
140
163
|
severity: "warning",
|
|
141
164
|
match: (t) => t.kind === "meta" && t.property === "og:title",
|
|
142
165
|
label: '<meta property="og:title">',
|
|
143
|
-
recommendation: 'Add <meta property="og:title">, or set openGraph.title on your meta component.'
|
|
166
|
+
recommendation: 'Add <meta property="og:title">, or set openGraph.title on your meta component.',
|
|
167
|
+
fix: {
|
|
168
|
+
description: 'Add <meta property="og:title">, or set openGraph.title on your meta component.',
|
|
169
|
+
snippet: '<svelte:head>\n <meta property="og:title" content="Page title" />\n</svelte:head>',
|
|
170
|
+
lang: "svelte"
|
|
171
|
+
}
|
|
144
172
|
});
|
|
145
173
|
var seo008JsonLd = headTagRule({
|
|
146
174
|
id: "SEO008",
|
|
@@ -148,7 +176,15 @@ var seo008JsonLd = headTagRule({
|
|
|
148
176
|
severity: "info",
|
|
149
177
|
match: (t) => t.kind === "jsonld",
|
|
150
178
|
label: 'JSON-LD (<script type="application/ld+json">)',
|
|
151
|
-
recommendation: "Add JSON-LD structured data, e.g. via <svelte:head> or a JsonLd component."
|
|
179
|
+
recommendation: "Add JSON-LD structured data, e.g. via <svelte:head> or a JsonLd component.",
|
|
180
|
+
fix: {
|
|
181
|
+
// Svelte ships <script> contents verbatim (the body is raw text, not Svelte
|
|
182
|
+
// markup), so use literal JSON here — an interpolation like {JSON.stringify(...)}
|
|
183
|
+
// would be emitted as that literal string and produce invalid JSON-LD.
|
|
184
|
+
description: "Add a JSON-LD <script> inside <svelte:head> with literal JSON (Svelte emits the script body as-is).",
|
|
185
|
+
snippet: '<svelte:head>\n <script type="application/ld+json">\n {\n "@context": "https://schema.org",\n "@type": "WebPage",\n "name": "Page title"\n }\n </script>\n</svelte:head>',
|
|
186
|
+
lang: "svelte"
|
|
187
|
+
}
|
|
152
188
|
});
|
|
153
189
|
|
|
154
190
|
// src/rules/seo/project-rules.ts
|
|
@@ -169,7 +205,12 @@ var seo006Robots = {
|
|
|
169
205
|
detection,
|
|
170
206
|
message: ctx.project.hasRobotsTxt ? "robots.txt" : "Missing robots.txt",
|
|
171
207
|
recommendation: "Add static/robots.txt or a src/routes/robots.txt/+server endpoint.",
|
|
172
|
-
docsUrl: "https://svelte-vitals.dev/rules/SEO006"
|
|
208
|
+
docsUrl: "https://svelte-vitals.dev/rules/SEO006",
|
|
209
|
+
fix: {
|
|
210
|
+
description: "Create static/robots.txt (or a src/routes/robots.txt/+server endpoint).",
|
|
211
|
+
snippet: "User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml",
|
|
212
|
+
lang: "text"
|
|
213
|
+
}
|
|
173
214
|
}
|
|
174
215
|
];
|
|
175
216
|
}
|
|
@@ -189,7 +230,12 @@ var seo007Sitemap = {
|
|
|
189
230
|
detection,
|
|
190
231
|
message: ctx.project.hasSitemap ? "sitemap.xml" : "Missing sitemap.xml",
|
|
191
232
|
recommendation: "Add static/sitemap.xml or a src/routes/sitemap.xml/+server endpoint.",
|
|
192
|
-
docsUrl: "https://svelte-vitals.dev/rules/SEO007"
|
|
233
|
+
docsUrl: "https://svelte-vitals.dev/rules/SEO007",
|
|
234
|
+
fix: {
|
|
235
|
+
description: "Create static/sitemap.xml (or a src/routes/sitemap.xml/+server endpoint).",
|
|
236
|
+
snippet: '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>https://example.com/</loc></url>\n</urlset>',
|
|
237
|
+
lang: "xml"
|
|
238
|
+
}
|
|
193
239
|
}
|
|
194
240
|
];
|
|
195
241
|
}
|
|
@@ -210,7 +256,12 @@ var seo009HtmlLang = {
|
|
|
210
256
|
detection,
|
|
211
257
|
message,
|
|
212
258
|
recommendation: 'Set <html lang="..."> in src/app.html.',
|
|
213
|
-
docsUrl: "https://svelte-vitals.dev/rules/SEO009"
|
|
259
|
+
docsUrl: "https://svelte-vitals.dev/rules/SEO009",
|
|
260
|
+
fix: {
|
|
261
|
+
description: "Set the lang attribute on <html> in src/app.html.",
|
|
262
|
+
snippet: '<html lang="en">',
|
|
263
|
+
lang: "html"
|
|
264
|
+
}
|
|
214
265
|
}
|
|
215
266
|
];
|
|
216
267
|
}
|
|
@@ -380,7 +431,8 @@ function issueOf(result) {
|
|
|
380
431
|
title: result.message,
|
|
381
432
|
detection: result.detection,
|
|
382
433
|
location: result.location,
|
|
383
|
-
recommendation: result.recommendation
|
|
434
|
+
recommendation: result.recommendation,
|
|
435
|
+
...result.fix ? { fix: result.fix } : {}
|
|
384
436
|
};
|
|
385
437
|
}
|
|
386
438
|
function formatJsonReport(results, config, meta) {
|
|
@@ -401,6 +453,52 @@ function formatJsonReport(results, config, meta) {
|
|
|
401
453
|
return JSON.stringify({ version: meta.version, score, scoreModel, summary, routes, siteIssues }, null, 2);
|
|
402
454
|
}
|
|
403
455
|
|
|
456
|
+
// src/reporter/agent.ts
|
|
457
|
+
var SEVERITY_RANK = { critical: 0, warning: 1, info: 2 };
|
|
458
|
+
function mdTags(text) {
|
|
459
|
+
return text.replace(/<[^>]+>/g, (tag) => `\`${tag}\``);
|
|
460
|
+
}
|
|
461
|
+
function formatAgentReport(results, config) {
|
|
462
|
+
const failing = results.filter((r) => classify(r, config) === "fail");
|
|
463
|
+
const lines = ["# svelte-vitals \u2014 SEO fixes", ""];
|
|
464
|
+
if (failing.length === 0) {
|
|
465
|
+
lines.push("No issues to fix.", "");
|
|
466
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
467
|
+
}
|
|
468
|
+
lines.push(
|
|
469
|
+
`${failing.length} issue(s) to fix, ordered most-severe first. Fix critical issues first; warning and info items improve SEO but do not fail the default build. Apply each fix below, then re-run \`svelte-vitals\` (or the build) to confirm each rule passes.`,
|
|
470
|
+
""
|
|
471
|
+
);
|
|
472
|
+
const groups = /* @__PURE__ */ new Map();
|
|
473
|
+
for (const r of failing) {
|
|
474
|
+
const key = r.location ?? r.route ?? "(project)";
|
|
475
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
476
|
+
groups.get(key).push(r);
|
|
477
|
+
}
|
|
478
|
+
const groupSeverity = (rs) => Math.min(...rs.map((r) => SEVERITY_RANK[effectiveSeverity(r, config)]));
|
|
479
|
+
const orderedGroups = [...groups.entries()].sort(
|
|
480
|
+
(a, b) => groupSeverity(a[1]) - groupSeverity(b[1]) || a[0].localeCompare(b[0])
|
|
481
|
+
);
|
|
482
|
+
for (const [loc, rs] of orderedGroups) {
|
|
483
|
+
rs.sort(
|
|
484
|
+
(x, y) => SEVERITY_RANK[effectiveSeverity(x, config)] - SEVERITY_RANK[effectiveSeverity(y, config)] || x.id.localeCompare(y.id)
|
|
485
|
+
);
|
|
486
|
+
lines.push(`## ${loc}`, "");
|
|
487
|
+
for (const r of rs) {
|
|
488
|
+
lines.push(`### ${r.id} \xB7 ${mdTags(r.message)} (${effectiveSeverity(r, config)})`);
|
|
489
|
+
if (r.fix) {
|
|
490
|
+
lines.push(`- Fix: ${mdTags(r.fix.description)}`);
|
|
491
|
+
if (r.fix.snippet) lines.push("", "```" + (r.fix.lang ?? "svelte"), r.fix.snippet, "```");
|
|
492
|
+
} else if (r.recommendation) {
|
|
493
|
+
lines.push(`- Fix: ${mdTags(r.recommendation)}`);
|
|
494
|
+
}
|
|
495
|
+
if (r.docsUrl) lines.push(`- Docs: ${r.docsUrl}`);
|
|
496
|
+
lines.push(`- Accept: re-run svelte-vitals; ${r.id} passes${r.route ? ` for ${r.route}` : ""}.`, "");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
500
|
+
}
|
|
501
|
+
|
|
404
502
|
// src/config-apply.ts
|
|
405
503
|
function selectRules(rules, config) {
|
|
406
504
|
return rules.filter((rule) => config.rules[rule.id] !== "off");
|
|
@@ -422,6 +520,7 @@ export {
|
|
|
422
520
|
defaultProject,
|
|
423
521
|
defineConfig,
|
|
424
522
|
effectiveSeverity,
|
|
523
|
+
formatAgentReport,
|
|
425
524
|
formatConsoleReport,
|
|
426
525
|
formatJsonReport,
|
|
427
526
|
hasFailureAtOrAbove,
|