svelte-vitals 0.29.0 → 0.31.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/README.md +3 -3
- package/dist/bin.js +5 -5
- package/dist/{chunk-AAUZ37EW.js → chunk-3YO2A3YF.js} +142 -58
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -33,16 +33,16 @@ Architecture Score: 100/100 (route avg 100)
|
|
|
33
33
|
|
|
34
34
|
Critical (2)
|
|
35
35
|
────────────────────────
|
|
36
|
-
✗
|
|
36
|
+
✗ seo/title-presence Missing <title>
|
|
37
37
|
/
|
|
38
38
|
src/routes/+page.svelte
|
|
39
|
-
✗
|
|
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
|
-
✗
|
|
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-
|
|
12
|
+
} from "./chunk-3YO2A3YF.js";
|
|
13
13
|
|
|
14
14
|
// src/bin.ts
|
|
15
15
|
import mri3 from "mri";
|
|
@@ -505,7 +505,7 @@ without a judgment call of its own.
|
|
|
505
505
|
none today, by design), no code edits, no commits, no formatters, no
|
|
506
506
|
dependency installs. Run svelte-vitals read-only, for evidence only.
|
|
507
507
|
3. **Plans must be fully self-contained.** The executor has zero context
|
|
508
|
-
from this conversation. Never write "fix it like
|
|
508
|
+
from this conversation. Never write "fix it like seo/title-presence above" \u2014 inline
|
|
509
509
|
the exact file, line, current code, and the exact fix (svelte-vitals'
|
|
510
510
|
\`fix.snippet\`/\`fix.description\` for the rule, quoted verbatim \u2014 see
|
|
511
511
|
below).
|
|
@@ -789,7 +789,7 @@ function buildConfigFileTemplate(opts = {}) {
|
|
|
789
789
|
const header = "// svelte-vitals config file \u2014 https://oekazuma.github.io/svelte-vitals/guides/configuration/\n";
|
|
790
790
|
const options = ` // treatDynamicAs: 'pass', // 'pass' | 'warn' | 'fail' \u2014 how {data.title}-style dynamic values are scored
|
|
791
791
|
// metaComponents: ['Seo'], // component names that resolve SEO tags into <head>
|
|
792
|
-
// rules: {}, // e.g. {
|
|
792
|
+
// rules: {}, // e.g. { 'seo/title-presence': 'off' } to disable a rule
|
|
793
793
|
// failOn: 'critical', // 'critical' | 'warning' | 'info'
|
|
794
794
|
// weights: {} // e.g. { seo: 2 } \u2014 per-category weight for the combined Health score`;
|
|
795
795
|
if (opts.useDefineConfig) {
|
|
@@ -998,8 +998,8 @@ function readInstalledViteVersion(io) {
|
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
1000
|
// src/ci/action-pin.generated.ts
|
|
1001
|
-
var ACTION_SHA = "
|
|
1002
|
-
var ACTION_VERSION = "0.3.
|
|
1001
|
+
var ACTION_SHA = "4625e1a6266d99282463c8882bbd600b14123d92";
|
|
1002
|
+
var ACTION_VERSION = "0.3.6";
|
|
1003
1003
|
|
|
1004
1004
|
// src/install/index.ts
|
|
1005
1005
|
function detectPackageManagerNear(io, appDir) {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
defineConfig,
|
|
18
18
|
selectRules,
|
|
19
19
|
applyRuleSeverities,
|
|
20
|
+
applyOverrides,
|
|
20
21
|
collectKitModuleFacts
|
|
21
22
|
} from "@svelte-vitals/core";
|
|
22
23
|
|
|
@@ -52,7 +53,8 @@ import { defaultConfig } from "@svelte-vitals/core";
|
|
|
52
53
|
// src/providers/source/project.ts
|
|
53
54
|
import {
|
|
54
55
|
ROBOTS_SOURCE_PATHS,
|
|
55
|
-
SITEMAP_SOURCE_PATHS
|
|
56
|
+
SITEMAP_SOURCE_PATHS,
|
|
57
|
+
findMinifyDisabled
|
|
56
58
|
} from "@svelte-vitals/core";
|
|
57
59
|
var ProjectError = class extends Error {
|
|
58
60
|
constructor(message) {
|
|
@@ -109,18 +111,39 @@ async function robotsRefsSitemap(rt, cwd) {
|
|
|
109
111
|
return void 0;
|
|
110
112
|
}
|
|
111
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
|
+
}
|
|
112
133
|
async function collectProjectFacts(rt, cwd) {
|
|
113
|
-
const [hasRobotsTxt, hasSitemap, htmlLang] = await Promise.all([
|
|
134
|
+
const [hasRobotsTxt, hasSitemap, htmlLang, viteMinifyDisabled] = await Promise.all([
|
|
114
135
|
existsAny(rt, cwd, ROBOTS_SOURCE_PATHS),
|
|
115
136
|
existsAny(rt, cwd, SITEMAP_SOURCE_PATHS),
|
|
116
|
-
detectAppHtmlLang(rt, cwd)
|
|
137
|
+
detectAppHtmlLang(rt, cwd),
|
|
138
|
+
detectViteMinifyDisabled(rt, cwd)
|
|
117
139
|
]);
|
|
118
140
|
const robotsReferencesSitemap = await robotsRefsSitemap(rt, cwd);
|
|
119
141
|
return {
|
|
120
142
|
hasRobotsTxt,
|
|
121
143
|
hasSitemap,
|
|
122
144
|
htmlLang,
|
|
123
|
-
...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {}
|
|
145
|
+
...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {},
|
|
146
|
+
...viteMinifyDisabled ? { viteMinifyDisabled } : {}
|
|
124
147
|
};
|
|
125
148
|
}
|
|
126
149
|
|
|
@@ -138,16 +161,16 @@ function exprValue(node) {
|
|
|
138
161
|
}
|
|
139
162
|
function resolveMetaObject(attr, keyMap) {
|
|
140
163
|
if (!attr) return { tags: [], opaque: false };
|
|
141
|
-
const expr = attr.value
|
|
164
|
+
const expr = attr.value !== true && !Array.isArray(attr.value) ? attr.value.expression : void 0;
|
|
142
165
|
if (!expr || expr.type !== "ObjectExpression") return { tags: [], opaque: true };
|
|
143
166
|
const tags = [];
|
|
144
167
|
let opaque = false;
|
|
145
|
-
for (const prop of expr.properties
|
|
146
|
-
if (prop
|
|
168
|
+
for (const prop of expr.properties) {
|
|
169
|
+
if (prop.type !== "Property" || prop.computed) {
|
|
147
170
|
opaque = true;
|
|
148
171
|
continue;
|
|
149
172
|
}
|
|
150
|
-
const key = prop.key
|
|
173
|
+
const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? prop.key.value : void 0;
|
|
151
174
|
const make = typeof key === "string" ? keyMap[key] : void 0;
|
|
152
175
|
if (make) tags.push(make(exprValue(prop.value)));
|
|
153
176
|
}
|
|
@@ -167,7 +190,7 @@ var TWITTER_KEYS = {
|
|
|
167
190
|
|
|
168
191
|
// src/providers/source/adapters/svelte-meta-tags.ts
|
|
169
192
|
function findAttr(attributes, name) {
|
|
170
|
-
return attributes.find((a) => a
|
|
193
|
+
return attributes.find((a) => a.type === "Attribute" && a.name === name);
|
|
171
194
|
}
|
|
172
195
|
var svelteMetaTagsAdapter = {
|
|
173
196
|
match(info) {
|
|
@@ -220,7 +243,7 @@ var svelteMetaTagsJsonLdAdapter = {
|
|
|
220
243
|
// src/providers/source/adapters/svelte-seo.ts
|
|
221
244
|
import { attrValueOf as attrValueOf2, attrTextOf as attrTextOf2 } from "@svelte-vitals/core";
|
|
222
245
|
function findAttr2(attributes, name) {
|
|
223
|
-
return attributes.find((a) => a
|
|
246
|
+
return attributes.find((a) => a.type === "Attribute" && a.name === name);
|
|
224
247
|
}
|
|
225
248
|
var svelteSeoAdapter = {
|
|
226
249
|
match(info) {
|
|
@@ -272,28 +295,31 @@ import {
|
|
|
272
295
|
// src/providers/source/imports.ts
|
|
273
296
|
function addImportsFromProgram(program, map) {
|
|
274
297
|
for (const node of program?.body ?? []) {
|
|
275
|
-
if (node
|
|
276
|
-
const source = String(node.source
|
|
277
|
-
for (const spec of node.specifiers
|
|
278
|
-
const local = spec
|
|
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;
|
|
279
302
|
if (!local) continue;
|
|
280
303
|
if (spec.type === "ImportDefaultSpecifier") {
|
|
281
304
|
map.set(local, { source, imported: "default" });
|
|
282
305
|
} else if (spec.type === "ImportSpecifier") {
|
|
283
|
-
|
|
306
|
+
const imported = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value;
|
|
307
|
+
map.set(local, { source, imported: typeof imported === "string" ? imported : local });
|
|
284
308
|
}
|
|
285
309
|
}
|
|
286
310
|
}
|
|
287
311
|
}
|
|
288
312
|
function collectImports(ast) {
|
|
289
|
-
const root = ast;
|
|
290
313
|
const map = /* @__PURE__ */ new Map();
|
|
291
|
-
addImportsFromProgram(
|
|
292
|
-
addImportsFromProgram(
|
|
314
|
+
addImportsFromProgram(ast.instance?.content, map);
|
|
315
|
+
addImportsFromProgram(ast.module?.content, map);
|
|
293
316
|
return map;
|
|
294
317
|
}
|
|
295
318
|
|
|
296
319
|
// src/providers/source/parse.ts
|
|
320
|
+
function childOf(node, key) {
|
|
321
|
+
return node[key];
|
|
322
|
+
}
|
|
297
323
|
function collectSvelteHeads(node, acc) {
|
|
298
324
|
if (Array.isArray(node)) {
|
|
299
325
|
for (const child of node) collectSvelteHeads(child, acc);
|
|
@@ -302,32 +328,33 @@ function collectSvelteHeads(node, acc) {
|
|
|
302
328
|
if (!node || typeof node !== "object") return;
|
|
303
329
|
if (node.type === "SvelteHead") acc.push(node);
|
|
304
330
|
for (const key of CHILD_NODE_KEYS) {
|
|
305
|
-
if (key in node) collectSvelteHeads(node
|
|
331
|
+
if (key in node) collectSvelteHeads(childOf(node, key), acc);
|
|
306
332
|
}
|
|
307
333
|
}
|
|
308
334
|
function tagsFromHead(head) {
|
|
309
335
|
const tags = [];
|
|
310
|
-
const children = head
|
|
336
|
+
const children = head.fragment.nodes;
|
|
311
337
|
for (const node of children) {
|
|
312
|
-
if (node
|
|
313
|
-
const titleNodes = node.fragment
|
|
338
|
+
if (node.type === "TitleElement") {
|
|
339
|
+
const titleNodes = node.fragment.nodes;
|
|
314
340
|
const text = textFromNodes(titleNodes);
|
|
315
341
|
tags.push({ kind: "title", value: valueFromNodes(titleNodes), ...text !== void 0 ? { text } : {} });
|
|
316
342
|
continue;
|
|
317
343
|
}
|
|
318
|
-
if (node
|
|
344
|
+
if (node.type !== "RegularElement") continue;
|
|
345
|
+
const attributes = node.attributes;
|
|
319
346
|
if (node.name === "meta") {
|
|
320
|
-
const charset = attrValue(
|
|
347
|
+
const charset = attrValue(attributes, "charset");
|
|
321
348
|
if (charset !== "absent") {
|
|
322
349
|
tags.push({ kind: "meta", name: "charset", value: charset });
|
|
323
350
|
continue;
|
|
324
351
|
}
|
|
325
|
-
const name = attrText(
|
|
326
|
-
const property = attrText(
|
|
327
|
-
const content = name === "robots" ? attrText(
|
|
352
|
+
const name = attrText(attributes, "name");
|
|
353
|
+
const property = attrText(attributes, "property");
|
|
354
|
+
const content = name === "robots" ? attrText(attributes, "content") : void 0;
|
|
328
355
|
const noindex = content !== void 0 && /(^|[\s,])(noindex|none)([\s,]|$)/i.test(content);
|
|
329
|
-
const contentValue = attrValue(
|
|
330
|
-
const descText = name === "description" && contentValue === "static" ? attrText(
|
|
356
|
+
const contentValue = attrValue(attributes, "content");
|
|
357
|
+
const descText = name === "description" && contentValue === "static" ? attrText(attributes, "content") : void 0;
|
|
331
358
|
tags.push({
|
|
332
359
|
kind: "meta",
|
|
333
360
|
...name ? { name } : {},
|
|
@@ -337,33 +364,33 @@ function tagsFromHead(head) {
|
|
|
337
364
|
...descText !== void 0 ? { text: descText } : {}
|
|
338
365
|
});
|
|
339
366
|
} else if (node.name === "link") {
|
|
340
|
-
const rel = attrText(
|
|
341
|
-
const hasAs = findAttr3(
|
|
342
|
-
const asLiteral = attrText(
|
|
343
|
-
const hasCrossorigin = findAttr3(
|
|
344
|
-
const hreflang = attrText(
|
|
345
|
-
const href = attrText(
|
|
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");
|
|
346
373
|
tags.push({
|
|
347
374
|
kind: "link",
|
|
348
375
|
...rel ? { rel } : {},
|
|
349
|
-
value: attrValue(
|
|
376
|
+
value: attrValue(attributes, "href"),
|
|
350
377
|
...hasAs ? { hasAs: true } : {},
|
|
351
378
|
...asLiteral ? { as: asLiteral } : {},
|
|
352
379
|
...hasCrossorigin ? { hasCrossorigin: true } : {},
|
|
353
|
-
// Keep a literal empty hreflang="" (present-but-invalid) so
|
|
380
|
+
// Keep a literal empty hreflang="" (present-but-invalid) so seo/hreflang can flag it.
|
|
354
381
|
...hreflang !== void 0 ? { hreflang } : {},
|
|
355
382
|
...href ? { href } : {}
|
|
356
383
|
});
|
|
357
384
|
} else if (node.name === "script") {
|
|
358
|
-
const type = attrText(
|
|
385
|
+
const type = attrText(attributes, "type");
|
|
359
386
|
if (type === "application/ld+json") {
|
|
360
|
-
const nodes = node.fragment
|
|
387
|
+
const nodes = node.fragment.nodes;
|
|
361
388
|
const raw = textFromNodes(nodes);
|
|
362
389
|
tags.push({ kind: "jsonld", value: valueFromNodes(nodes), ...raw !== void 0 ? { jsonld: raw } : {} });
|
|
363
390
|
} else {
|
|
364
|
-
const src = attrText(
|
|
391
|
+
const src = attrText(attributes, "src");
|
|
365
392
|
if (src) {
|
|
366
|
-
const blocking = findAttr3(
|
|
393
|
+
const blocking = findAttr3(attributes, "defer") === void 0 && findAttr3(attributes, "async") === void 0 && type !== "module";
|
|
367
394
|
tags.push({ kind: "script", value: "static", href: src, ...blocking ? { blocking: true } : {} });
|
|
368
395
|
}
|
|
369
396
|
}
|
|
@@ -377,16 +404,16 @@ function collectComponents(node, acc) {
|
|
|
377
404
|
return;
|
|
378
405
|
}
|
|
379
406
|
if (!node || typeof node !== "object") return;
|
|
380
|
-
if (node.type === "Component"
|
|
381
|
-
const attributes = node.attributes
|
|
407
|
+
if (node.type === "Component") {
|
|
408
|
+
const attributes = node.attributes;
|
|
382
409
|
acc.push({
|
|
383
410
|
name: node.name,
|
|
384
411
|
attributes,
|
|
385
|
-
hasSpread: attributes.some((a) => a
|
|
412
|
+
hasSpread: attributes.some((a) => a.type === "SpreadAttribute")
|
|
386
413
|
});
|
|
387
414
|
}
|
|
388
415
|
for (const key of CHILD_NODE_KEYS) {
|
|
389
|
-
if (key in node) collectComponents(node
|
|
416
|
+
if (key in node) collectComponents(childOf(node, key), acc);
|
|
390
417
|
}
|
|
391
418
|
}
|
|
392
419
|
function collectImages(node, source, acc) {
|
|
@@ -396,8 +423,8 @@ function collectImages(node, source, acc) {
|
|
|
396
423
|
}
|
|
397
424
|
if (!node || typeof node !== "object") return;
|
|
398
425
|
if (node.type === "RegularElement" && node.name === "img") {
|
|
399
|
-
const attrs = node.attributes
|
|
400
|
-
const hasSpread =
|
|
426
|
+
const attrs = node.attributes;
|
|
427
|
+
const hasSpread = node.attributes.some((a) => a.type === "SpreadAttribute");
|
|
401
428
|
acc.push({
|
|
402
429
|
hasWidth: hasSpread || Boolean(findAttr3(attrs, "width")),
|
|
403
430
|
hasHeight: hasSpread || Boolean(findAttr3(attrs, "height")),
|
|
@@ -410,7 +437,7 @@ function collectImages(node, source, acc) {
|
|
|
410
437
|
});
|
|
411
438
|
}
|
|
412
439
|
for (const key of CHILD_NODE_KEYS) {
|
|
413
|
-
if (key in node) collectImages(node
|
|
440
|
+
if (key in node) collectImages(childOf(node, key), source, acc);
|
|
414
441
|
}
|
|
415
442
|
}
|
|
416
443
|
function collectHeadings(node, source, acc) {
|
|
@@ -420,23 +447,23 @@ function collectHeadings(node, source, acc) {
|
|
|
420
447
|
}
|
|
421
448
|
if (!node || typeof node !== "object") return;
|
|
422
449
|
if (node.type === "SvelteHead") return;
|
|
423
|
-
if (node.type === "RegularElement" &&
|
|
450
|
+
if (node.type === "RegularElement" && /^h[1-6]$/.test(node.name)) {
|
|
424
451
|
acc.push({ level: Number(node.name[1]), line: lineOf(source, node.start) });
|
|
425
452
|
}
|
|
426
453
|
for (const key of CHILD_NODE_KEYS) {
|
|
427
|
-
if (key in node) collectHeadings(node
|
|
454
|
+
if (key in node) collectHeadings(childOf(node, key), source, acc);
|
|
428
455
|
}
|
|
429
456
|
}
|
|
430
457
|
function parseFile(source, filename) {
|
|
431
458
|
const ast = parse(source, { modern: true, filename });
|
|
432
459
|
const heads = [];
|
|
433
|
-
collectSvelteHeads(ast.fragment
|
|
460
|
+
collectSvelteHeads(ast.fragment, heads);
|
|
434
461
|
const components = [];
|
|
435
|
-
collectComponents(ast.fragment
|
|
462
|
+
collectComponents(ast.fragment, components);
|
|
436
463
|
const images = [];
|
|
437
|
-
collectImages(ast.fragment
|
|
464
|
+
collectImages(ast.fragment, source, images);
|
|
438
465
|
const headings = [];
|
|
439
|
-
collectHeadings(ast.fragment
|
|
466
|
+
collectHeadings(ast.fragment, source, headings);
|
|
440
467
|
return {
|
|
441
468
|
headTags: heads.flatMap(tagsFromHead),
|
|
442
469
|
components,
|
|
@@ -1132,7 +1159,8 @@ var CONFIG_FILENAMES = ["svelte-vitals.config.mjs", "svelte-vitals.config.js", "
|
|
|
1132
1159
|
var CATEGORIES = ["seo", "performance", "correctness", "security", "architecture"];
|
|
1133
1160
|
var TREAT_DYNAMIC_AS_VALUES = ["pass", "warn", "fail"];
|
|
1134
1161
|
var FAIL_ON_VALUES = ["critical", "warning", "info"];
|
|
1135
|
-
var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["treatDynamicAs", "metaComponents", "rules", "failOn", "weights"]);
|
|
1162
|
+
var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["treatDynamicAs", "metaComponents", "rules", "failOn", "weights", "overrides"]);
|
|
1163
|
+
var RULE_SETTING_VALUES = ["off", "critical", "warning", "info"];
|
|
1136
1164
|
function isPlainObject2(value) {
|
|
1137
1165
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1138
1166
|
}
|
|
@@ -1183,6 +1211,58 @@ function validateConfigFile(raw, path) {
|
|
|
1183
1211
|
}
|
|
1184
1212
|
config.rules = rules;
|
|
1185
1213
|
}
|
|
1214
|
+
if (raw.overrides !== void 0) {
|
|
1215
|
+
if (!Array.isArray(raw.overrides)) {
|
|
1216
|
+
throw new Error(`${path}: overrides must be an array of { route/files, rules } entries.`);
|
|
1217
|
+
}
|
|
1218
|
+
const isGlob = (v) => typeof v === "string" && v.length > 0;
|
|
1219
|
+
const isGlobs = (v) => isGlob(v) || Array.isArray(v) && v.length > 0 && v.every(isGlob);
|
|
1220
|
+
const overrides = [];
|
|
1221
|
+
raw.overrides.forEach((entry, i) => {
|
|
1222
|
+
if (!isPlainObject2(entry)) {
|
|
1223
|
+
throw new Error(`${path}: overrides[${i}] must be an object with 'route' and/or 'files', and 'rules'.`);
|
|
1224
|
+
}
|
|
1225
|
+
if (entry.route !== void 0 && !isGlobs(entry.route)) {
|
|
1226
|
+
throw new Error(
|
|
1227
|
+
`${path}: overrides[${i}].route must be a non-empty string or a non-empty array of non-empty strings.`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
if (entry.files !== void 0 && !isGlobs(entry.files)) {
|
|
1231
|
+
throw new Error(
|
|
1232
|
+
`${path}: overrides[${i}].files must be a non-empty string or a non-empty array of non-empty strings.`
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
if (entry.route === void 0 && entry.files === void 0) {
|
|
1236
|
+
throw new Error(`${path}: overrides[${i}] must set 'route' and/or 'files' to scope the override.`);
|
|
1237
|
+
}
|
|
1238
|
+
if (!isPlainObject2(entry.rules)) {
|
|
1239
|
+
throw new Error(`${path}: overrides[${i}].rules must be an object of rule-id/category \u2192 setting.`);
|
|
1240
|
+
}
|
|
1241
|
+
if (Object.keys(entry.rules).length === 0) {
|
|
1242
|
+
throw new Error(`${path}: overrides[${i}].rules must contain at least one rule id or category.`);
|
|
1243
|
+
}
|
|
1244
|
+
const nonCategoryKeys = Object.keys(entry.rules).filter((k) => !CATEGORIES.includes(k));
|
|
1245
|
+
const unknown = findUnknownRuleIds(nonCategoryKeys);
|
|
1246
|
+
if (unknown.length > 0) {
|
|
1247
|
+
throw new Error(
|
|
1248
|
+
`${path}: unknown rule id(s) or categories in overrides[${i}].rules: ${unknown.join(", ")}. Known categories: ${CATEGORIES.join(", ")}. Known rule ids: ${knownRuleIds().join(", ")}`
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
for (const [key, setting] of Object.entries(entry.rules)) {
|
|
1252
|
+
if (!RULE_SETTING_VALUES.includes(setting)) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`${path}: overrides[${i}].rules.${key}: invalid setting '${String(setting)}'; expected ${RULE_SETTING_VALUES.join("|")}.`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
overrides.push({
|
|
1259
|
+
...entry.route !== void 0 ? { route: entry.route } : {},
|
|
1260
|
+
...entry.files !== void 0 ? { files: entry.files } : {},
|
|
1261
|
+
rules: entry.rules
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
config.overrides = overrides;
|
|
1265
|
+
}
|
|
1186
1266
|
if (raw.weights !== void 0) {
|
|
1187
1267
|
if (!isPlainObject2(raw.weights)) {
|
|
1188
1268
|
throw new Error(`${path}: weights must be an object of category \u2192 number.`);
|
|
@@ -1303,7 +1383,8 @@ async function analyzeProject(opts = {}) {
|
|
|
1303
1383
|
metaComponents: opts.metaComponents ?? file?.metaComponents ?? [],
|
|
1304
1384
|
rules: opts.rules ?? file?.rules ?? {},
|
|
1305
1385
|
failOn: opts.failOn ?? file?.failOn ?? "critical",
|
|
1306
|
-
...weights !== void 0 ? { weights } : {}
|
|
1386
|
+
...weights !== void 0 ? { weights } : {},
|
|
1387
|
+
...file?.overrides !== void 0 ? { overrides: file.overrides } : {}
|
|
1307
1388
|
});
|
|
1308
1389
|
await detectProject(rt, cwd);
|
|
1309
1390
|
const matches = routeMatcher(opts.route);
|
|
@@ -1316,8 +1397,11 @@ async function analyzeProject(opts = {}) {
|
|
|
1316
1397
|
const kitModules = opts.route ? [] : await collectKitModuleFacts(rt, cwd);
|
|
1317
1398
|
const selected = selectRules(allRules2, config);
|
|
1318
1399
|
const rules = opts.categories ? selected.filter((r) => opts.categories.includes(r.category)) : selected;
|
|
1319
|
-
const results =
|
|
1320
|
-
|
|
1400
|
+
const results = applyOverrides(
|
|
1401
|
+
applyRuleSeverities(
|
|
1402
|
+
await runRules(rules, { heads, images, headings, components, project, config, kitModules }),
|
|
1403
|
+
config
|
|
1404
|
+
),
|
|
1321
1405
|
config
|
|
1322
1406
|
);
|
|
1323
1407
|
return { results, config, version: readPackageVersion(), warnings };
|
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:
|
|
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 (
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
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.
|
|
47
|
+
"svelte": "^5.56.6",
|
|
48
48
|
"tinyglobby": "^0.2.17",
|
|
49
|
-
"@svelte-vitals/core": "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": {
|