@svelte-vitals/core 0.0.1 → 0.2.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
@@ -15,6 +15,14 @@ interface Detection {
15
15
  presence: Presence;
16
16
  value: Value;
17
17
  }
18
+ /** Project-wide facts precomputed by the runtime layer for project-scope rules (design §10). */
19
+ interface Project {
20
+ hasRobotsTxt: boolean;
21
+ hasSitemap: boolean;
22
+ /** <html lang> from app.html: presence 'own' when the attribute exists ('none' otherwise); value 'static' if non-empty, 'absent' if empty. */
23
+ htmlLang: Detection;
24
+ }
25
+ declare const defaultProject: Project;
18
26
  /** A single rule finding for one route (or the whole project). */
19
27
  interface Result {
20
28
  /** Rule id, e.g. 'SEO001'. */
@@ -33,8 +41,16 @@ type Scope = 'route' | 'project';
33
41
  type Category = 'seo' | 'performance' | 'a11y' | 'maintainability';
34
42
  /** How dynamic (`{data.title}`) values are treated by scoring (design §4, §12). */
35
43
  type TreatDynamicAs = 'pass' | 'warn' | 'fail';
44
+ /** Per-rule override: disable, or change severity. */
45
+ type RuleSetting = 'off' | Severity;
36
46
  interface Config {
37
47
  treatDynamicAs: TreatDynamicAs;
48
+ /** Component names treated as meta sources of unknown content (design §11 layer 4). */
49
+ metaComponents: string[];
50
+ /** Per-rule overrides keyed by rule id (design §6). */
51
+ rules: Record<string, RuleSetting>;
52
+ /** Minimum severity that fails the run / CI (design §6). */
53
+ failOn: Severity;
38
54
  }
39
55
  declare const defaultConfig: Config;
40
56
  /** Merge user config over defaults. Identity helper for config files (design §6). */
@@ -92,12 +108,13 @@ interface ResolvedHead {
92
108
  /** Supplies ResolvedHead[] for a project. The only piece that differs per mode. */
93
109
  interface HeadProvider {
94
110
  mode: 'static' | 'rendered';
95
- collect(rt: Runtime, cwd: string): Promise<ResolvedHead[]>;
111
+ collect(rt: Runtime, cwd: string, config?: Config): Promise<ResolvedHead[]>;
96
112
  }
97
113
 
98
114
  /** Input given to every rule. Mode-independent: rules see only ResolvedHead[] (design §8, §10). */
99
115
  interface RuleContext {
100
116
  heads: ResolvedHead[];
117
+ project: Project;
101
118
  config: Config;
102
119
  }
103
120
  interface Rule {
@@ -120,7 +137,7 @@ interface Rule {
120
137
  *
121
138
  * presence 'none' → penalized (nothing set anywhere)
122
139
  * value 'absent' → penalized (tag present but empty)
123
- * value 'dynamic' → penalized only when treatDynamicAs is 'fail'
140
+ * value 'dynamic' → penalized when treatDynamicAs is not 'pass' (warn or fail)
124
141
  * otherwise (static/inherited) → not penalized
125
142
  */
126
143
  declare function isPenalized(detection: Detection, treatDynamicAs: TreatDynamicAs): boolean;
@@ -139,9 +156,31 @@ declare function runRules(rules: Rule[], ctx: RuleContext): Promise<Result[]>;
139
156
  */
140
157
  declare const seo001Title: Rule;
141
158
 
142
- /** Rule registry. Slice 0 ships SEO001 only; later slices append here. */
159
+ declare const seo002Description: Rule;
160
+ declare const seo003Canonical: Rule;
161
+ declare const seo004OgImage: Rule;
162
+ declare const seo005OgTitle: Rule;
163
+ declare const seo008JsonLd: Rule;
164
+
165
+ declare const seo006Robots: Rule;
166
+ declare const seo007Sitemap: Rule;
167
+ declare const seo009HtmlLang: Rule;
168
+
143
169
  declare const allRules: Rule[];
144
170
 
171
+ interface HeadTagRuleOptions {
172
+ id: string;
173
+ title: string;
174
+ severity: Severity;
175
+ /** Identifies the tag this rule looks for. */
176
+ match: (tag: HeadTag) => boolean;
177
+ /** Short human label, e.g. 'description'. */
178
+ label: string;
179
+ recommendation: string;
180
+ }
181
+ /** Build a route-scope rule asserting the presence of a single head tag (design §11). */
182
+ declare function headTagRule(opts: HeadTagRuleOptions): Rule;
183
+
145
184
  interface Summary {
146
185
  critical: number;
147
186
  warning: number;
@@ -154,15 +193,46 @@ interface Summary {
154
193
  /** Classify a single result for display/scoring (design §7, §12). */
155
194
  type Classification = 'fail' | 'pass' | 'dynamic';
156
195
  declare function classify(result: Result, config: Config): Classification;
196
+ /** A penalized dynamic finding is a warning under treatDynamicAs 'warn'; otherwise the rule's severity. */
197
+ declare function effectiveSeverity(result: Result, config: Config): Severity;
157
198
  declare function summarize(results: Result[], config: Config): Summary;
158
199
  /** Whether the run should fail the build/CI per the minimum failing severity. */
159
200
  declare function hasFailureAtOrAbove(summary: Summary, min: Severity): boolean;
160
201
 
202
+ interface ConsoleReportOptions {
203
+ byRoute?: boolean;
204
+ }
161
205
  /**
162
206
  * Render results as a console report string (design §7). Pure: returns a string,
163
- * the caller is responsible for printing. Slice 0 lists failures grouped by
164
- * severity, then passing routes (with a marker for dynamic values).
207
+ * the caller is responsible for printing. Prepends a score header; when byRoute is
208
+ * set, adds a per-route score tree.
165
209
  */
166
- declare function formatConsoleReport(results: Result[], config: Config): string;
210
+ declare function formatConsoleReport(results: Result[], config: Config, options?: ConsoleReportOptions): string;
211
+
212
+ /** Render results as the documented JSON report string (design §7). */
213
+ declare function formatJsonReport(results: Result[], config: Config, meta: {
214
+ version: string;
215
+ }): string;
216
+
217
+ /** Drop rules disabled via config (design §6). */
218
+ declare function selectRules(rules: Rule[], config: Config): Rule[];
219
+ /** Apply per-rule severity overrides to results (design §6). */
220
+ declare function applyRuleSeverities(results: Result[], config: Config): Result[];
221
+
222
+ interface ScoreModel {
223
+ routeAverage: number;
224
+ sitePenalty: number;
225
+ /** Headline cap value when it actually lowered the score, else null. */
226
+ criticalCap: number | null;
227
+ }
228
+ interface ScoreResult {
229
+ score: number;
230
+ scoreModel: ScoreModel;
231
+ }
232
+ interface ScoreOptions {
233
+ applyCriticalCap?: boolean;
234
+ }
235
+ /** Compute the headline score and its breakdown (design §12). */
236
+ declare function computeScore(results: Result[], config: Config, options?: ScoreOptions): ScoreResult;
167
237
 
168
- export { type Category, type Classification, type Config, type Detection, type HeadProvider, type HeadTag, type Presence, type ResolvedHead, type Result, type Rule, type RuleContext, type Runtime, type Scope, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, classify, defaultConfig, defineConfig, formatConsoleReport, hasFailureAtOrAbove, isPenalized, runRules, seo001Title, summarize };
238
+ export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type HeadProvider, type HeadTag, type Presence, type Project, type ResolvedHead, type Result, type Rule, type RuleContext, type RuleSetting, type Runtime, 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 };
package/dist/index.js CHANGED
@@ -1,6 +1,14 @@
1
1
  // src/types.ts
2
+ var defaultProject = {
3
+ hasRobotsTxt: false,
4
+ hasSitemap: false,
5
+ htmlLang: { presence: "none", value: "absent" }
6
+ };
2
7
  var defaultConfig = {
3
- treatDynamicAs: "pass"
8
+ treatDynamicAs: "pass",
9
+ metaComponents: [],
10
+ rules: {},
11
+ failOn: "critical"
4
12
  };
5
13
  function defineConfig(config = {}) {
6
14
  return { ...defaultConfig, ...config };
@@ -10,7 +18,7 @@ function defineConfig(config = {}) {
10
18
  function isPenalized(detection, treatDynamicAs) {
11
19
  if (detection.presence === "none") return true;
12
20
  if (detection.value === "absent") return true;
13
- if (detection.value === "dynamic") return treatDynamicAs === "fail";
21
+ if (detection.value === "dynamic") return treatDynamicAs !== "pass";
14
22
  return false;
15
23
  }
16
24
 
@@ -57,8 +65,157 @@ var seo001Title = {
57
65
  }
58
66
  };
59
67
 
68
+ // src/rules/seo/head-tag-rule.ts
69
+ function detect(head, match) {
70
+ const tag = head.tags.find(match);
71
+ return tag ? { presence: tag.presence, value: tag.value } : { presence: "none", value: "absent" };
72
+ }
73
+ function headTagRule(opts) {
74
+ const docsUrl = `https://svelte-vitals.dev/rules/${opts.id}`;
75
+ return {
76
+ id: opts.id,
77
+ title: opts.title,
78
+ category: "seo",
79
+ severity: opts.severity,
80
+ scope: "route",
81
+ async check(ctx) {
82
+ return ctx.heads.map((head) => {
83
+ const detection = detect(head, opts.match);
84
+ const message = detection.presence === "none" ? `Missing ${opts.label}` : detection.value === "absent" ? `Empty ${opts.label}` : opts.label;
85
+ return {
86
+ id: opts.id,
87
+ severity: opts.severity,
88
+ detection,
89
+ route: head.route,
90
+ location: head.file,
91
+ message,
92
+ recommendation: opts.recommendation,
93
+ docsUrl
94
+ };
95
+ });
96
+ }
97
+ };
98
+ }
99
+
100
+ // src/rules/seo/seo002-005-008.ts
101
+ var seo002Description = headTagRule({
102
+ id: "SEO002",
103
+ title: "Description presence",
104
+ severity: "critical",
105
+ match: (t) => t.kind === "meta" && t.name === "description",
106
+ label: '<meta name="description">',
107
+ recommendation: 'Add a <meta name="description"> in <svelte:head>, or set the description on your meta component.'
108
+ });
109
+ var seo003Canonical = headTagRule({
110
+ id: "SEO003",
111
+ title: "Canonical URL",
112
+ severity: "warning",
113
+ match: (t) => t.kind === "link" && t.rel === "canonical",
114
+ label: '<link rel="canonical">',
115
+ recommendation: 'Add <link rel="canonical"> in <svelte:head>, or set the canonical prop on your meta component.'
116
+ });
117
+ var seo004OgImage = headTagRule({
118
+ id: "SEO004",
119
+ title: "Open Graph image",
120
+ severity: "warning",
121
+ match: (t) => t.kind === "meta" && t.property === "og:image",
122
+ label: '<meta property="og:image">',
123
+ recommendation: 'Add <meta property="og:image">, or set openGraph.images on your meta component.'
124
+ });
125
+ var seo005OgTitle = headTagRule({
126
+ id: "SEO005",
127
+ title: "Open Graph title",
128
+ severity: "warning",
129
+ match: (t) => t.kind === "meta" && t.property === "og:title",
130
+ label: '<meta property="og:title">',
131
+ recommendation: 'Add <meta property="og:title">, or set openGraph.title on your meta component.'
132
+ });
133
+ var seo008JsonLd = headTagRule({
134
+ id: "SEO008",
135
+ title: "JSON-LD structured data",
136
+ severity: "info",
137
+ match: (t) => t.kind === "jsonld",
138
+ label: 'JSON-LD (<script type="application/ld+json">)',
139
+ recommendation: "Add JSON-LD structured data, e.g. via <svelte:head> or a JsonLd component."
140
+ });
141
+
142
+ // src/rules/seo/project-rules.ts
143
+ var present = { presence: "own", value: "static" };
144
+ var absent = { presence: "none", value: "absent" };
145
+ var seo006Robots = {
146
+ id: "SEO006",
147
+ title: "robots.txt",
148
+ category: "seo",
149
+ severity: "warning",
150
+ scope: "project",
151
+ async check(ctx) {
152
+ const detection = ctx.project.hasRobotsTxt ? present : absent;
153
+ return [
154
+ {
155
+ id: "SEO006",
156
+ severity: "warning",
157
+ detection,
158
+ message: ctx.project.hasRobotsTxt ? "robots.txt" : "Missing robots.txt",
159
+ recommendation: "Add static/robots.txt or a src/routes/robots.txt/+server endpoint.",
160
+ docsUrl: "https://svelte-vitals.dev/rules/SEO006"
161
+ }
162
+ ];
163
+ }
164
+ };
165
+ var seo007Sitemap = {
166
+ id: "SEO007",
167
+ title: "sitemap.xml",
168
+ category: "seo",
169
+ severity: "warning",
170
+ scope: "project",
171
+ async check(ctx) {
172
+ const detection = ctx.project.hasSitemap ? present : absent;
173
+ return [
174
+ {
175
+ id: "SEO007",
176
+ severity: "warning",
177
+ detection,
178
+ message: ctx.project.hasSitemap ? "sitemap.xml" : "Missing sitemap.xml",
179
+ recommendation: "Add static/sitemap.xml or a src/routes/sitemap.xml/+server endpoint.",
180
+ docsUrl: "https://svelte-vitals.dev/rules/SEO007"
181
+ }
182
+ ];
183
+ }
184
+ };
185
+ var seo009HtmlLang = {
186
+ id: "SEO009",
187
+ title: "<html lang>",
188
+ category: "seo",
189
+ severity: "warning",
190
+ scope: "project",
191
+ async check(ctx) {
192
+ const detection = ctx.project.htmlLang;
193
+ const message = detection.presence === "none" ? "Missing <html lang>" : detection.value === "absent" ? "Empty <html lang>" : "<html lang>";
194
+ return [
195
+ {
196
+ id: "SEO009",
197
+ severity: "warning",
198
+ detection,
199
+ message,
200
+ recommendation: 'Set <html lang="..."> in src/app.html.',
201
+ docsUrl: "https://svelte-vitals.dev/rules/SEO009"
202
+ }
203
+ ];
204
+ }
205
+ };
206
+
60
207
  // src/rules/index.ts
61
- var allRules = [seo001Title];
208
+ var allRules = [
209
+ seo001Title,
210
+ seo002Description,
211
+ seo003Canonical,
212
+ seo004OgImage,
213
+ seo005OgTitle,
214
+ seo006Robots,
215
+ seo007Sitemap,
216
+ seo008JsonLd,
217
+ seo009HtmlLang
218
+ ];
62
219
 
63
220
  // src/summary.ts
64
221
  function classify(result, config) {
@@ -66,12 +223,16 @@ function classify(result, config) {
66
223
  if (result.detection.value === "dynamic") return "dynamic";
67
224
  return "pass";
68
225
  }
226
+ function effectiveSeverity(result, config) {
227
+ if (result.detection.value === "dynamic" && config.treatDynamicAs === "warn") return "warning";
228
+ return result.severity;
229
+ }
69
230
  function summarize(results, config) {
70
231
  const summary = { critical: 0, warning: 0, info: 0, passed: 0, dynamic: 0 };
71
232
  for (const result of results) {
72
233
  const cls = classify(result, config);
73
234
  if (cls === "fail") {
74
- summary[severityKey(result.severity)] += 1;
235
+ summary[effectiveSeverity(result, config)] += 1;
75
236
  } else {
76
237
  summary.passed += 1;
77
238
  if (cls === "dynamic") summary.dynamic += 1;
@@ -82,10 +243,55 @@ function summarize(results, config) {
82
243
  function hasFailureAtOrAbove(summary, min) {
83
244
  const order = ["info", "warning", "critical"];
84
245
  const threshold = order.indexOf(min);
85
- return order.some((sev, idx) => idx >= threshold && summary[severityKey(sev)] > 0);
246
+ return order.some((sev, idx) => idx >= threshold && summary[sev] > 0);
86
247
  }
87
- function severityKey(severity) {
88
- return severity;
248
+
249
+ // src/scoring/score.ts
250
+ var DEDUCTION = { critical: 15, warning: 5, info: 1 };
251
+ var CRITICAL_CAP = 79;
252
+ function clamp(n) {
253
+ return Math.max(0, Math.min(100, n));
254
+ }
255
+ function computeScore(results, config, options = {}) {
256
+ const routeResults = results.filter((r) => r.route !== void 0);
257
+ const projectResults = results.filter((r) => r.route === void 0);
258
+ const routeScores = /* @__PURE__ */ new Map();
259
+ for (const r of routeResults) if (!routeScores.has(r.route)) routeScores.set(r.route, 100);
260
+ let anyCritical = false;
261
+ const routeRuleMax = /* @__PURE__ */ new Map();
262
+ for (const r of routeResults) {
263
+ if (!isPenalized(r.detection, config.treatDynamicAs)) continue;
264
+ const sev = effectiveSeverity(r, config);
265
+ if (sev === "critical") anyCritical = true;
266
+ const route = r.route;
267
+ let perRule = routeRuleMax.get(route);
268
+ if (!perRule) routeRuleMax.set(route, perRule = /* @__PURE__ */ new Map());
269
+ const prev = perRule.get(r.id) ?? 0;
270
+ if (DEDUCTION[sev] > prev) perRule.set(r.id, DEDUCTION[sev]);
271
+ }
272
+ for (const [route, perRule] of routeRuleMax) {
273
+ let deduction = 0;
274
+ for (const d of perRule.values()) deduction += d;
275
+ routeScores.set(route, routeScores.get(route) - deduction);
276
+ }
277
+ const scores = [...routeScores.values()].map(clamp);
278
+ const routeAverage = scores.length ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
279
+ const projectRuleMax = /* @__PURE__ */ new Map();
280
+ for (const r of projectResults) {
281
+ if (!isPenalized(r.detection, config.treatDynamicAs)) continue;
282
+ const sev = effectiveSeverity(r, config);
283
+ if (sev === "critical") anyCritical = true;
284
+ const prev = projectRuleMax.get(r.id) ?? 0;
285
+ if (DEDUCTION[sev] > prev) projectRuleMax.set(r.id, DEDUCTION[sev]);
286
+ }
287
+ let sitePenalty = 0;
288
+ for (const deduction of projectRuleMax.values()) sitePenalty += deduction;
289
+ const applyCap = options.applyCriticalCap ?? true;
290
+ const uncapped = routeAverage - sitePenalty;
291
+ const capBinds = applyCap && anyCritical && uncapped > CRITICAL_CAP;
292
+ const criticalCap = capBinds ? CRITICAL_CAP : null;
293
+ const score = capBinds ? CRITICAL_CAP : uncapped;
294
+ return { score: clamp(score), scoreModel: { routeAverage, sitePenalty, criticalCap } };
89
295
  }
90
296
 
91
297
  // src/reporter/console.ts
@@ -95,13 +301,37 @@ var SEVERITY_TITLE = {
95
301
  warning: "Warnings",
96
302
  info: "Info"
97
303
  };
98
- function formatConsoleReport(results, config) {
304
+ function scoreHeader(results, config) {
305
+ const { score, scoreModel } = computeScore(results, config);
306
+ const parts = [`route avg ${scoreModel.routeAverage}`];
307
+ if (scoreModel.sitePenalty > 0) parts.push(`site \u2212${scoreModel.sitePenalty}`);
308
+ if (scoreModel.criticalCap !== null) parts.push(`capped at ${scoreModel.criticalCap}: critical present`);
309
+ return `SEO Score: ${score}/100 (${parts.join(" \xB7 ")})`;
310
+ }
311
+ function byRouteTree(results, config) {
312
+ const routes = /* @__PURE__ */ new Map();
313
+ for (const r of results) {
314
+ if (r.route === void 0) continue;
315
+ if (!routes.has(r.route)) routes.set(r.route, []);
316
+ routes.get(r.route).push(r);
317
+ }
318
+ const lines = ["By route", RULE];
319
+ for (const [route, rs] of [...routes.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
320
+ const { score } = computeScore(rs, config, { applyCriticalCap: false });
321
+ lines.push(`${route.padEnd(28)} ${score}`);
322
+ for (const r of rs.filter((x) => classify(x, config) === "fail")) {
323
+ lines.push(` \u2717 ${r.id} ${r.message}`);
324
+ }
325
+ }
326
+ lines.push("");
327
+ return lines;
328
+ }
329
+ function formatConsoleReport(results, config, options = {}) {
99
330
  const summary = summarize(results, config);
100
- const lines = [];
101
- lines.push("Svelte Vitals \xB7 SEO (static mode)", "");
331
+ const lines = ["Svelte Vitals \xB7 SEO (static mode)", "", scoreHeader(results, config), ""];
102
332
  const failures = results.filter((r) => classify(r, config) === "fail");
103
333
  for (const severity of ["critical", "warning", "info"]) {
104
- const bucket = failures.filter((r) => r.severity === severity);
334
+ const bucket = failures.filter((r) => effectiveSeverity(r, config) === severity);
105
335
  if (bucket.length === 0) continue;
106
336
  lines.push(`${SEVERITY_TITLE[severity]} (${bucket.length})`, RULE);
107
337
  for (const r of bucket) {
@@ -121,20 +351,73 @@ function formatConsoleReport(results, config) {
121
351
  }
122
352
  lines.push("");
123
353
  }
124
- if (summary.dynamic > 0) {
125
- lines.push("\u21AF = set dynamically (verified at runtime).");
126
- }
354
+ if (options.byRoute) lines.push(...byRouteTree(results, config));
355
+ if (summary.dynamic > 0) lines.push("\u21AF = set dynamically (verified at runtime).");
127
356
  return lines.join("\n").replace(/\n+$/, "\n");
128
357
  }
358
+
359
+ // src/reporter/json.ts
360
+ function issueOf(result) {
361
+ return {
362
+ id: result.id,
363
+ title: result.message,
364
+ detection: result.detection,
365
+ location: result.location,
366
+ recommendation: result.recommendation
367
+ };
368
+ }
369
+ function formatJsonReport(results, config, meta) {
370
+ const { score, scoreModel } = computeScore(results, config);
371
+ const summary = summarize(results, config);
372
+ const routeMap = /* @__PURE__ */ new Map();
373
+ for (const r of results) {
374
+ if (r.route === void 0) continue;
375
+ if (!routeMap.has(r.route)) routeMap.set(r.route, { route: r.route, results: [] });
376
+ routeMap.get(r.route).results.push(r);
377
+ }
378
+ const routes = [...routeMap.values()].sort((a, b) => a.route.localeCompare(b.route)).map(({ route, results: rs }) => ({
379
+ route,
380
+ score: computeScore(rs, config, { applyCriticalCap: false }).score,
381
+ issues: rs.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }))
382
+ }));
383
+ const siteIssues = results.filter((r) => r.route === void 0 && isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }));
384
+ return JSON.stringify({ version: meta.version, score, scoreModel, summary, routes, siteIssues }, null, 2);
385
+ }
386
+
387
+ // src/config-apply.ts
388
+ function selectRules(rules, config) {
389
+ return rules.filter((rule) => config.rules[rule.id] !== "off");
390
+ }
391
+ function applyRuleSeverities(results, config) {
392
+ return results.map((result) => {
393
+ const setting = config.rules[result.id];
394
+ return setting && setting !== "off" ? { ...result, severity: setting } : result;
395
+ });
396
+ }
129
397
  export {
130
398
  allRules,
399
+ applyRuleSeverities,
131
400
  classify,
401
+ computeScore,
132
402
  defaultConfig,
403
+ defaultProject,
133
404
  defineConfig,
405
+ effectiveSeverity,
134
406
  formatConsoleReport,
407
+ formatJsonReport,
135
408
  hasFailureAtOrAbove,
409
+ headTagRule,
136
410
  isPenalized,
137
411
  runRules,
412
+ selectRules,
138
413
  seo001Title,
414
+ seo002Description,
415
+ seo003Canonical,
416
+ seo004OgImage,
417
+ seo005OgTitle,
418
+ seo006Robots,
419
+ seo007Sitemap,
420
+ seo008JsonLd,
421
+ seo009HtmlLang,
139
422
  summarize
140
423
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",