shelfware 0.0.1 → 0.1.1
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/.output/nitro.json +15 -0
- package/.output/public/_nuxt/6IstVYrV.js +1 -0
- package/.output/public/_nuxt/9C5WUlLE.js +36 -0
- package/.output/public/_nuxt/BDNMzG2s.js +1 -0
- package/.output/public/_nuxt/BW3xJdEx.js +1 -0
- package/.output/public/_nuxt/BXXtFil5.js +1 -0
- package/.output/public/_nuxt/BZuUK3cr.js +1 -0
- package/.output/public/_nuxt/BeLyMmd4.js +1 -0
- package/.output/public/_nuxt/C_VgD9ZI.js +3 -0
- package/.output/public/_nuxt/DB5Vxbte.js +2 -0
- package/.output/public/_nuxt/DMg0j7gv.js +14 -0
- package/.output/public/_nuxt/builds/latest.json +1 -0
- package/.output/public/_nuxt/builds/meta/e0d2b6a3-3a30-44b7-8c79-a56f66400005.json +1 -0
- package/.output/public/_nuxt/entry.b3_DJTEt.css +2 -0
- package/.output/public/_nuxt/error-404.CHsn7XF-.css +1 -0
- package/.output/public/_nuxt/error-500.Bawx2bLr.css +1 -0
- package/.output/server/chunks/_/api-handler.mjs +44 -0
- package/.output/server/chunks/_/batch.mjs +27 -0
- package/.output/server/chunks/_/catalog.mjs +1115 -0
- package/.output/server/chunks/_/detail.mjs +16 -0
- package/.output/server/chunks/_/error-500.mjs +19 -0
- package/.output/server/chunks/_/quarantine.mjs +159 -0
- package/.output/server/chunks/nitro/nitro.mjs +5399 -0
- package/.output/server/chunks/routes/api/_..._.mjs +16 -0
- package/.output/server/chunks/routes/api/health.get.mjs +13 -0
- package/.output/server/chunks/routes/api/skills/_id/file.get.mjs +25 -0
- package/.output/server/chunks/routes/api/skills/_id_.get.mjs +25 -0
- package/.output/server/chunks/routes/api/skills/_id_.put.mjs +74 -0
- package/.output/server/chunks/routes/api/skills/delete.post.mjs +48 -0
- package/.output/server/chunks/routes/api/skills/quarantine.post.mjs +26 -0
- package/.output/server/chunks/routes/api/skills/restore.post.mjs +26 -0
- package/.output/server/chunks/routes/api/skills.get.mjs +41 -0
- package/.output/server/chunks/routes/renderer.mjs +341 -0
- package/.output/server/chunks/virtual/_virtual_spa-template.mjs +3 -0
- package/.output/server/chunks/virtual/precomputed.mjs +3 -0
- package/.output/server/index.mjs +9 -0
- package/.output/server/node_modules/@babel/parser/lib/index.js +14611 -0
- package/.output/server/node_modules/@babel/parser/package.json +50 -0
- package/.output/server/node_modules/@vue/compiler-core/dist/compiler-core.cjs.prod.js +6801 -0
- package/.output/server/node_modules/@vue/compiler-core/package.json +58 -0
- package/.output/server/node_modules/@vue/compiler-dom/dist/compiler-dom.cjs.prod.js +692 -0
- package/.output/server/node_modules/@vue/compiler-dom/package.json +57 -0
- package/.output/server/node_modules/@vue/compiler-ssr/dist/compiler-ssr.cjs.js +1420 -0
- package/.output/server/node_modules/@vue/compiler-ssr/package.json +34 -0
- package/.output/server/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js +1903 -0
- package/.output/server/node_modules/@vue/reactivity/package.json +55 -0
- package/.output/server/node_modules/@vue/runtime-core/dist/runtime-core.cjs.prod.js +6980 -0
- package/.output/server/node_modules/@vue/runtime-core/package.json +52 -0
- package/.output/server/node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.prod.js +1867 -0
- package/.output/server/node_modules/@vue/runtime-dom/package.json +60 -0
- package/.output/server/node_modules/@vue/server-renderer/dist/server-renderer.cjs.prod.js +904 -0
- package/.output/server/node_modules/@vue/server-renderer/package.json +53 -0
- package/.output/server/node_modules/@vue/shared/dist/shared.cjs.prod.js +636 -0
- package/.output/server/node_modules/@vue/shared/package.json +47 -0
- package/.output/server/node_modules/devalue/index.js +17 -0
- package/.output/server/node_modules/devalue/package.json +39 -0
- package/.output/server/node_modules/devalue/src/base64.js +60 -0
- package/.output/server/node_modules/devalue/src/constants.js +12 -0
- package/.output/server/node_modules/devalue/src/operations.js +193 -0
- package/.output/server/node_modules/devalue/src/parse.js +274 -0
- package/.output/server/node_modules/devalue/src/stringify.js +428 -0
- package/.output/server/node_modules/devalue/src/uneval.js +606 -0
- package/.output/server/node_modules/devalue/src/utils.js +185 -0
- package/.output/server/node_modules/entities/dist/commonjs/decode-codepoint.js +77 -0
- package/.output/server/node_modules/entities/dist/commonjs/decode.js +568 -0
- package/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-html.js +7 -0
- package/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-xml.js +7 -0
- package/.output/server/node_modules/entities/dist/commonjs/internal/bin-trie-flags.js +21 -0
- package/.output/server/node_modules/entities/dist/commonjs/internal/decode-shared.js +31 -0
- package/.output/server/node_modules/entities/dist/commonjs/package.json +3 -0
- package/.output/server/node_modules/entities/package.json +120 -0
- package/.output/server/node_modules/estree-walker/dist/umd/estree-walker.js +344 -0
- package/.output/server/node_modules/estree-walker/package.json +37 -0
- package/.output/server/node_modules/hookable/dist/index.mjs +257 -0
- package/.output/server/node_modules/hookable/package.json +53 -0
- package/.output/server/node_modules/nostics/dist/formatters/ansi.mjs +19 -0
- package/.output/server/node_modules/nostics/dist/index.mjs +187 -0
- package/.output/server/node_modules/nostics/package.json +125 -0
- package/.output/server/node_modules/source-map-js/lib/array-set.js +121 -0
- package/.output/server/node_modules/source-map-js/lib/base64-vlq.js +140 -0
- package/.output/server/node_modules/source-map-js/lib/base64.js +67 -0
- package/.output/server/node_modules/source-map-js/lib/binary-search.js +111 -0
- package/.output/server/node_modules/source-map-js/lib/mapping-list.js +79 -0
- package/.output/server/node_modules/source-map-js/lib/quick-sort.js +132 -0
- package/.output/server/node_modules/source-map-js/lib/source-map-consumer.js +1188 -0
- package/.output/server/node_modules/source-map-js/lib/source-map-generator.js +444 -0
- package/.output/server/node_modules/source-map-js/lib/source-node.js +413 -0
- package/.output/server/node_modules/source-map-js/lib/util.js +594 -0
- package/.output/server/node_modules/source-map-js/package.json +71 -0
- package/.output/server/node_modules/source-map-js/source-map.js +8 -0
- package/.output/server/node_modules/ufo/dist/index.mjs +645 -0
- package/.output/server/node_modules/ufo/package.json +48 -0
- package/.output/server/node_modules/unhead/dist/client.mjs +22 -0
- package/.output/server/node_modules/unhead/dist/legacy.mjs +33 -0
- package/.output/server/node_modules/unhead/dist/minify.mjs +183 -0
- package/.output/server/node_modules/unhead/dist/parser.mjs +518 -0
- package/.output/server/node_modules/unhead/dist/plugins.mjs +720 -0
- package/.output/server/node_modules/unhead/dist/server.mjs +99 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.-hZVKou0.mjs +400 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.BEmVMkeA.mjs +673 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.BGFxPGPQ.mjs +47 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.BQWSw36o.mjs +192 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Bf_fPVYA.mjs +6 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Bjbp1C8D.mjs +224 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Bm4Y6XQI.mjs +17 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.C5Bksi2B.mjs +49 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.CGPOfp5O.mjs +32 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.CHEy9ana.mjs +7 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.D7HkBzZn.mjs +59 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Dssd7L5I.mjs +196 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.jEy9BmRq.mjs +264 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.oZ7pkTV1.mjs +19 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.tQnRaXfX.mjs +240 -0
- package/.output/server/node_modules/unhead/dist/stream/client.mjs +33 -0
- package/.output/server/node_modules/unhead/dist/stream/iife.mjs +2 -0
- package/.output/server/node_modules/unhead/dist/stream/server.mjs +445 -0
- package/.output/server/node_modules/unhead/dist/utils.mjs +7 -0
- package/.output/server/node_modules/unhead/package.json +179 -0
- package/.output/server/node_modules/vue/dist/vue.cjs.js +80 -0
- package/.output/server/node_modules/vue/dist/vue.cjs.prod.js +66 -0
- package/.output/server/node_modules/vue/index.js +7 -0
- package/.output/server/node_modules/vue/index.mjs +1 -0
- package/.output/server/node_modules/vue/package.json +112 -0
- package/.output/server/node_modules/vue/server-renderer/index.mjs +1 -0
- package/.output/server/node_modules/vue-bundle-renderer/dist/runtime.mjs +278 -0
- package/.output/server/node_modules/vue-bundle-renderer/package.json +54 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/.output/server/node_modules/yaml/dist/compose/composer.js +224 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/.output/server/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/.output/server/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/.output/server/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/.output/server/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/.output/server/node_modules/yaml/dist/doc/Document.js +337 -0
- package/.output/server/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/.output/server/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/.output/server/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/.output/server/node_modules/yaml/dist/doc/directives.js +178 -0
- package/.output/server/node_modules/yaml/dist/errors.js +62 -0
- package/.output/server/node_modules/yaml/dist/index.js +50 -0
- package/.output/server/node_modules/yaml/dist/log.js +19 -0
- package/.output/server/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/.output/server/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/.output/server/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/.output/server/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/.output/server/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/.output/server/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/.output/server/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/.output/server/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/.output/server/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/.output/server/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/.output/server/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/.output/server/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/.output/server/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/.output/server/node_modules/yaml/dist/parse/cst.js +112 -0
- package/.output/server/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/.output/server/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/.output/server/node_modules/yaml/dist/parse/parser.js +980 -0
- package/.output/server/node_modules/yaml/dist/public-api.js +107 -0
- package/.output/server/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/.output/server/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/.output/server/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/.output/server/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/.output/server/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/.output/server/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/.output/server/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/.output/server/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/.output/server/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/.output/server/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/.output/server/node_modules/yaml/dist/schema/tags.js +99 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/.output/server/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/.output/server/node_modules/yaml/dist/visit.js +236 -0
- package/.output/server/node_modules/yaml/package.json +97 -0
- package/.output/server/package.json +28 -0
- package/LICENSE +21 -0
- package/README.md +57 -1
- package/bin/launch.mjs +216 -0
- package/bin/shelfware.mjs +11 -0
- package/package.json +64 -5
- package/cli.mjs +0 -3
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
export { A as AliasSortingPlugin, D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin } from './shared/unhead.tQnRaXfX.mjs';
|
|
2
|
+
import { d as defineHeadPlugin } from './shared/unhead.CHEy9ana.mjs';
|
|
3
|
+
export { F as FlatMetaPlugin, S as SafeInputPlugin } from './shared/unhead.jEy9BmRq.mjs';
|
|
4
|
+
import { h as hasContent } from './shared/unhead.CGPOfp5O.mjs';
|
|
5
|
+
import { minifyJS, minifyCSS, minifyJSON } from './minify.mjs';
|
|
6
|
+
import { u as unpackMeta } from './shared/unhead.BQWSw36o.mjs';
|
|
7
|
+
import { t as tagInputFromRuntime, u as tagPredicates, i as inputShapeFromRuntime, b as inputShapePredicates, U as URL_META_KEYS, v as titleInputFromRuntime, h as headInputPredicates } from './shared/unhead.BEmVMkeA.mjs';
|
|
8
|
+
import './shared/unhead.BGFxPGPQ.mjs';
|
|
9
|
+
|
|
10
|
+
const META_TRANSFORMABLE_URL = [
|
|
11
|
+
"og:url",
|
|
12
|
+
"og:image",
|
|
13
|
+
"og:image:url",
|
|
14
|
+
"og:image:secure_url",
|
|
15
|
+
"twitter:image",
|
|
16
|
+
"twitter:image:src",
|
|
17
|
+
"og:video",
|
|
18
|
+
"og:video:url",
|
|
19
|
+
"og:video:secure_url",
|
|
20
|
+
"og:audio",
|
|
21
|
+
"og:audio:url",
|
|
22
|
+
"og:audio:secure_url",
|
|
23
|
+
"twitter:player",
|
|
24
|
+
"twitter:player:stream"
|
|
25
|
+
];
|
|
26
|
+
const LINK_REL_RESOLVABLE = /* @__PURE__ */ new Set([
|
|
27
|
+
"canonical",
|
|
28
|
+
"next",
|
|
29
|
+
"prev",
|
|
30
|
+
"alternate",
|
|
31
|
+
"author",
|
|
32
|
+
"license",
|
|
33
|
+
"help",
|
|
34
|
+
"search",
|
|
35
|
+
"pingback"
|
|
36
|
+
]);
|
|
37
|
+
const META_CANONICAL_URL = /* @__PURE__ */ new Set([
|
|
38
|
+
"og:url"
|
|
39
|
+
]);
|
|
40
|
+
function CanonicalPlugin(options) {
|
|
41
|
+
return (head) => {
|
|
42
|
+
let host = options.canonicalHost || (!head.ssr ? window.location.origin : "");
|
|
43
|
+
if (!host.startsWith("http") && !host.startsWith("//")) {
|
|
44
|
+
host = `https://${host}`;
|
|
45
|
+
}
|
|
46
|
+
host = new URL(host).origin;
|
|
47
|
+
const whitelist = options.queryWhitelist !== void 0 ? options.queryWhitelist : [];
|
|
48
|
+
function normalizeCanonicalUrl(url) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = new URL(url, host);
|
|
51
|
+
parsed.hash = "";
|
|
52
|
+
if (whitelist !== false && parsed.search) {
|
|
53
|
+
const filtered = new URLSearchParams();
|
|
54
|
+
for (const key of whitelist) {
|
|
55
|
+
if (parsed.searchParams.has(key)) {
|
|
56
|
+
for (const value of parsed.searchParams.getAll(key)) {
|
|
57
|
+
filtered.append(key, value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
parsed.search = filtered.toString();
|
|
62
|
+
}
|
|
63
|
+
if (options.trailingSlash === true && !parsed.pathname.endsWith("/")) {
|
|
64
|
+
parsed.pathname = `${parsed.pathname}/`;
|
|
65
|
+
} else if (options.trailingSlash === false && parsed.pathname !== "/" && parsed.pathname.endsWith("/")) {
|
|
66
|
+
parsed.pathname = parsed.pathname.slice(0, -1);
|
|
67
|
+
}
|
|
68
|
+
return parsed.toString();
|
|
69
|
+
} catch {
|
|
70
|
+
return url;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resolvePath(path) {
|
|
74
|
+
if (options?.customResolver) {
|
|
75
|
+
return options.customResolver(path);
|
|
76
|
+
}
|
|
77
|
+
if (path.startsWith("http") || path.startsWith("//"))
|
|
78
|
+
return path;
|
|
79
|
+
try {
|
|
80
|
+
return new URL(path, host).toString();
|
|
81
|
+
} catch {
|
|
82
|
+
return path;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
key: "canonical",
|
|
87
|
+
hooks: {
|
|
88
|
+
"tags:resolve": (ctx) => {
|
|
89
|
+
for (const tag of ctx.tags) {
|
|
90
|
+
const metaKey = tag.props?.property || tag.props?.name;
|
|
91
|
+
if (tag.tag === "meta" && META_TRANSFORMABLE_URL.includes(metaKey)) {
|
|
92
|
+
tag.props.content = resolvePath(tag.props.content);
|
|
93
|
+
if (META_CANONICAL_URL.has(metaKey)) {
|
|
94
|
+
tag.props.content = normalizeCanonicalUrl(tag.props.content);
|
|
95
|
+
}
|
|
96
|
+
} else if (tag.tag === "link" && LINK_REL_RESOLVABLE.has(tag.props.rel)) {
|
|
97
|
+
const isCanonical = tag.props.rel === "canonical";
|
|
98
|
+
tag.props.href = resolvePath(tag.props.href);
|
|
99
|
+
if (isCanonical) {
|
|
100
|
+
tag.props.href = normalizeCanonicalUrl(tag.props.href);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function InferSeoMetaPlugin(options = {}) {
|
|
111
|
+
return defineHeadPlugin((head) => {
|
|
112
|
+
if (options.twitterCard !== false) {
|
|
113
|
+
head.push({
|
|
114
|
+
meta: [
|
|
115
|
+
{
|
|
116
|
+
name: "twitter:card",
|
|
117
|
+
content: options.twitterCard || "summary_large_image",
|
|
118
|
+
tagPriority: "low"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
head.push({
|
|
124
|
+
meta: [
|
|
125
|
+
// content is intentionally omitted - inferred from title/description by the hook below
|
|
126
|
+
{
|
|
127
|
+
"property": "og:title",
|
|
128
|
+
"tagPriority": "low",
|
|
129
|
+
"data-infer": ""
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"property": "og:description",
|
|
133
|
+
"tagPriority": "low",
|
|
134
|
+
"data-infer": ""
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
key: "infer-seo-meta",
|
|
140
|
+
hooks: {
|
|
141
|
+
"tags:beforeResolve": ({ tagMap }) => {
|
|
142
|
+
let title = head._titleTemplate || head._title;
|
|
143
|
+
const ogTitle = tagMap.get("meta:og:title");
|
|
144
|
+
if (typeof ogTitle?.props["data-infer"] !== "undefined") {
|
|
145
|
+
if (typeof title === "function") {
|
|
146
|
+
title = title(head._title);
|
|
147
|
+
}
|
|
148
|
+
title = hasContent(title) ? String(title) : void 0;
|
|
149
|
+
ogTitle.props.content = options.ogTitle ? options.ogTitle(title) : title || "";
|
|
150
|
+
ogTitle.processTemplateParams = true;
|
|
151
|
+
}
|
|
152
|
+
const descriptionValue = tagMap.get("meta:description")?.props?.content;
|
|
153
|
+
const description = hasContent(descriptionValue) ? String(descriptionValue) : void 0;
|
|
154
|
+
const ogDescription = tagMap.get("meta:og:description");
|
|
155
|
+
if (typeof ogDescription?.props["data-infer"] !== "undefined") {
|
|
156
|
+
ogDescription.props.content = options.ogDescription ? options.ogDescription(description) : description || "";
|
|
157
|
+
ogDescription.processTemplateParams = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}, "infer-seo-meta");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const JSON_TYPES = /* @__PURE__ */ new Set(["application/json", "application/ld+json", "speculationrules", "importmap"]);
|
|
166
|
+
function MinifyPlugin(options) {
|
|
167
|
+
const jsMinify = options?.js === false ? false : options?.js || minifyJS;
|
|
168
|
+
const cssMinify = options?.css === false ? false : options?.css || minifyCSS;
|
|
169
|
+
const jsonMinify = options?.json !== false;
|
|
170
|
+
const omitLineBreaks = options?.omitLineBreaks === true;
|
|
171
|
+
return {
|
|
172
|
+
key: "minify",
|
|
173
|
+
hooks: {
|
|
174
|
+
"ssr:render": (ctx) => {
|
|
175
|
+
if (omitLineBreaks)
|
|
176
|
+
ctx.options.omitLineBreaks = true;
|
|
177
|
+
for (const tag of ctx.tags) {
|
|
178
|
+
const content = tag.innerHTML;
|
|
179
|
+
if (!content || content.length < 20)
|
|
180
|
+
continue;
|
|
181
|
+
if (tag.tag === "script") {
|
|
182
|
+
const type = tag.props?.type;
|
|
183
|
+
if (type && JSON_TYPES.has(type)) {
|
|
184
|
+
if (jsonMinify) {
|
|
185
|
+
const min = minifyJSON(content);
|
|
186
|
+
if (min.length < content.length)
|
|
187
|
+
tag.innerHTML = min;
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (jsMinify) {
|
|
192
|
+
const min = jsMinify(content);
|
|
193
|
+
if (min.length < content.length)
|
|
194
|
+
tag.innerHTML = min;
|
|
195
|
+
}
|
|
196
|
+
} else if (tag.tag === "style" && cssMinify) {
|
|
197
|
+
const min = cssMinify(content);
|
|
198
|
+
if (min.length < content.length)
|
|
199
|
+
tag.innerHTML = min;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const TEMPLATE_PARAM_RE = /%\w+(?:\.\w+)?%/;
|
|
208
|
+
const AT_PREFIX_RE = /^at\s+/;
|
|
209
|
+
const SLACK_TWITTER_META_NAMES = /* @__PURE__ */ new Set([
|
|
210
|
+
"twitter:data1",
|
|
211
|
+
"twitter:data2",
|
|
212
|
+
"twitter:label1",
|
|
213
|
+
"twitter:label2"
|
|
214
|
+
]);
|
|
215
|
+
const PREDICATE_SEVERITY = {
|
|
216
|
+
"defer-on-module-script": "info",
|
|
217
|
+
"deprecated-prop-body": "warn",
|
|
218
|
+
"deprecated-prop-children": "warn",
|
|
219
|
+
"deprecated-prop-hid-vmid": "warn",
|
|
220
|
+
"empty-meta-content": "warn",
|
|
221
|
+
"html-in-title": "warn",
|
|
222
|
+
"invalid-input-shape": "warn",
|
|
223
|
+
"non-absolute-canonical": "warn",
|
|
224
|
+
"numeric-tag-priority": "info",
|
|
225
|
+
"possible-typo": "warn",
|
|
226
|
+
"preload-font-crossorigin": "warn",
|
|
227
|
+
"preload-missing-as": "warn",
|
|
228
|
+
"robots-conflict": "warn",
|
|
229
|
+
"script-src-with-content": "warn",
|
|
230
|
+
"streamed-tag-hidden-from-bots": "warn",
|
|
231
|
+
"twitter-handle-missing-at": "warn",
|
|
232
|
+
"viewport-user-scalable": "info"
|
|
233
|
+
};
|
|
234
|
+
function isAbsoluteUrl(url) {
|
|
235
|
+
return url.startsWith("http://") || url.startsWith("https://");
|
|
236
|
+
}
|
|
237
|
+
function extractOrigin(url) {
|
|
238
|
+
if (!isAbsoluteUrl(url))
|
|
239
|
+
return void 0;
|
|
240
|
+
const slash = url.indexOf("/", url.indexOf("//") + 2);
|
|
241
|
+
return slash === -1 ? url : url.slice(0, slash);
|
|
242
|
+
}
|
|
243
|
+
function resolveSeverity(config, fallback) {
|
|
244
|
+
if (config == null)
|
|
245
|
+
return fallback;
|
|
246
|
+
return Array.isArray(config) ? config[0] : config;
|
|
247
|
+
}
|
|
248
|
+
function resolveOptions(config, id, defaults) {
|
|
249
|
+
const entry = config[id];
|
|
250
|
+
if (Array.isArray(entry))
|
|
251
|
+
return { ...defaults, ...entry[1] };
|
|
252
|
+
return defaults;
|
|
253
|
+
}
|
|
254
|
+
function captureSource(root) {
|
|
255
|
+
const stack = new Error("source").stack;
|
|
256
|
+
if (!stack)
|
|
257
|
+
return void 0;
|
|
258
|
+
const lines = stack.split("\n");
|
|
259
|
+
for (let i = 4; i < lines.length; i++) {
|
|
260
|
+
const line = lines[i].trim();
|
|
261
|
+
if (line && !line.includes("node_modules") && !line.includes("unhead/src/")) {
|
|
262
|
+
let source = line.replace(AT_PREFIX_RE, "");
|
|
263
|
+
if (root) {
|
|
264
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
265
|
+
source = source.replace(prefix, "./");
|
|
266
|
+
}
|
|
267
|
+
return source;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
function createInputShapeObserver() {
|
|
273
|
+
let remainingRootKeys;
|
|
274
|
+
let resolvedValues;
|
|
275
|
+
const resolver = (key, value) => {
|
|
276
|
+
if (key === void 0) {
|
|
277
|
+
if (value?.constructor === Object) {
|
|
278
|
+
remainingRootKeys = new Set(Object.keys(value));
|
|
279
|
+
resolvedValues = /* @__PURE__ */ new Map();
|
|
280
|
+
} else {
|
|
281
|
+
remainingRootKeys = void 0;
|
|
282
|
+
resolvedValues = void 0;
|
|
283
|
+
}
|
|
284
|
+
} else if (remainingRootKeys?.delete(key)) {
|
|
285
|
+
resolvedValues?.set(key, value);
|
|
286
|
+
}
|
|
287
|
+
return value;
|
|
288
|
+
};
|
|
289
|
+
resolver._static = true;
|
|
290
|
+
return {
|
|
291
|
+
resolver,
|
|
292
|
+
take() {
|
|
293
|
+
const input = resolvedValues ? Object.fromEntries(resolvedValues) : void 0;
|
|
294
|
+
remainingRootKeys = void 0;
|
|
295
|
+
resolvedValues = void 0;
|
|
296
|
+
return input;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const BOT_HEAD_META_NAMES = /* @__PURE__ */ new Set(["description", "robots", "googlebot", "bingbot", "slurp", "keywords"]);
|
|
301
|
+
const BOT_HEAD_META_EQUIVS = /* @__PURE__ */ new Set(["refresh", "content-language"]);
|
|
302
|
+
const BOT_HEAD_LINK_RELS = /* @__PURE__ */ new Set(["canonical", "alternate", "amphtml", "prev", "next", "author", "license"]);
|
|
303
|
+
const BOT_HEAD_META_PREFIX_RE = /^(?:og|twitter|article|book|profile|fb|al|music|video|place|product):/;
|
|
304
|
+
const JSON_LD_TYPE_RE = /^[\t\n\f\r ]*application\/ld\+json[\t\n\f\r ]*(?:;|$)/i;
|
|
305
|
+
const REL_SEPARATOR_RE = /[\t\n\f\r ]+/;
|
|
306
|
+
const SAFE_BLOCKED_RELS = /* @__PURE__ */ new Set(["canonical", "modulepreload", "prerender", "preload", "prefetch", "dns-prefetch", "preconnect", "manifest", "pingback"]);
|
|
307
|
+
function relTokens(value) {
|
|
308
|
+
return String(value || "").toLowerCase().split(REL_SEPARATOR_RE);
|
|
309
|
+
}
|
|
310
|
+
function* expandPendingTag(tag) {
|
|
311
|
+
if (tag._safe && tag.tag === "link" && relTokens(tag.props.rel).some((rel) => SAFE_BLOCKED_RELS.has(rel)))
|
|
312
|
+
return;
|
|
313
|
+
if (tag.props.body)
|
|
314
|
+
tag.tagPosition = "bodyClose";
|
|
315
|
+
if (tag.tag !== "_flatMeta") {
|
|
316
|
+
yield tag;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
for (const props of unpackMeta(tag.props))
|
|
320
|
+
yield { ...tag, tag: "meta", props };
|
|
321
|
+
}
|
|
322
|
+
function isHiddenFromBots(tag, writesBodyTags) {
|
|
323
|
+
const props = tag.props;
|
|
324
|
+
if (tag.tag === "script")
|
|
325
|
+
return !writesBodyTags && JSON_LD_TYPE_RE.test(String(props.type || ""));
|
|
326
|
+
if (tag.tagPosition?.startsWith("body"))
|
|
327
|
+
return false;
|
|
328
|
+
switch (tag.tag) {
|
|
329
|
+
case "title":
|
|
330
|
+
case "titleTemplate":
|
|
331
|
+
case "base":
|
|
332
|
+
return true;
|
|
333
|
+
case "meta": {
|
|
334
|
+
if (BOT_HEAD_META_NAMES.has(String(props.name || "").toLowerCase()))
|
|
335
|
+
return true;
|
|
336
|
+
if (BOT_HEAD_META_EQUIVS.has(String(props["http-equiv"] || "").toLowerCase()))
|
|
337
|
+
return true;
|
|
338
|
+
return BOT_HEAD_META_PREFIX_RE.test(String(props.property || props.name || "").toLowerCase());
|
|
339
|
+
}
|
|
340
|
+
case "link":
|
|
341
|
+
return relTokens(props.rel).some((rel) => BOT_HEAD_LINK_RELS.has(rel));
|
|
342
|
+
default:
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function describeTag(tag) {
|
|
347
|
+
const props = tag.props;
|
|
348
|
+
if (tag.tag === "meta")
|
|
349
|
+
return `meta[${props.property ? `property="${props.property}"` : props["http-equiv"] ? `http-equiv="${props["http-equiv"]}"` : `name="${props.name}"`}]`;
|
|
350
|
+
if (tag.tag === "link")
|
|
351
|
+
return `link[rel="${props.rel}"]`;
|
|
352
|
+
if (tag.tag === "script")
|
|
353
|
+
return 'script[type="application/ld+json"]';
|
|
354
|
+
return tag.tag;
|
|
355
|
+
}
|
|
356
|
+
function ValidatePlugin(options = {}) {
|
|
357
|
+
const ruleConfig = options.rules || {};
|
|
358
|
+
const root = options.root;
|
|
359
|
+
const only = options.only && new Set(options.only);
|
|
360
|
+
const pluginKey = options.key || "validate";
|
|
361
|
+
function severityFor(id, fallback) {
|
|
362
|
+
if (only && !only.has(id))
|
|
363
|
+
return "off";
|
|
364
|
+
return resolveSeverity(ruleConfig[id], fallback);
|
|
365
|
+
}
|
|
366
|
+
return defineHeadPlugin((head) => {
|
|
367
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
368
|
+
const pendingInputDiagnostics = [];
|
|
369
|
+
const inputShapeObserver = createInputShapeObserver();
|
|
370
|
+
head.resolvedOptions.propResolvers = [
|
|
371
|
+
...head.resolvedOptions.propResolvers || [],
|
|
372
|
+
inputShapeObserver.resolver
|
|
373
|
+
];
|
|
374
|
+
const hooks = head.hooks;
|
|
375
|
+
if (hooks) {
|
|
376
|
+
const _callHook = hooks.callHook.bind(hooks);
|
|
377
|
+
let warnedAsyncHook = false;
|
|
378
|
+
hooks.callHook = (name, ...args) => {
|
|
379
|
+
const result = _callHook(name, ...args);
|
|
380
|
+
if (result?.then && !warnedAsyncHook && !only) {
|
|
381
|
+
warnedAsyncHook = true;
|
|
382
|
+
console.warn(`[unhead] promise ignored: ${name}`);
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const _push = head.push.bind(head);
|
|
388
|
+
head.push = (input, opts) => {
|
|
389
|
+
if (opts?.mode && severityFor("deprecated-option-mode", "warn") !== "off") {
|
|
390
|
+
console.warn(`[unhead] "mode: '${opts.mode}'" option was removed in v3. Use the appropriate createHead import (unhead/client or unhead/server) instead.`);
|
|
391
|
+
}
|
|
392
|
+
const source = captureSource(root);
|
|
393
|
+
const active = _push(input, opts);
|
|
394
|
+
if (source)
|
|
395
|
+
stacks.set(active._i, source);
|
|
396
|
+
const _dispose = active.dispose;
|
|
397
|
+
active.dispose = () => {
|
|
398
|
+
stacks.delete(active._i);
|
|
399
|
+
_dispose();
|
|
400
|
+
};
|
|
401
|
+
return active;
|
|
402
|
+
};
|
|
403
|
+
function dispatch(rules, mode) {
|
|
404
|
+
const head_ = head;
|
|
405
|
+
head_._validationRules = mode === "replace" ? rules : [...head_._validationRules || [], ...rules];
|
|
406
|
+
if (!rules.length)
|
|
407
|
+
return;
|
|
408
|
+
if (options.onReport) {
|
|
409
|
+
options.onReport(rules);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
for (const rule of rules) {
|
|
413
|
+
const loc = rule.source ? ` (${rule.source})` : "";
|
|
414
|
+
console.warn(`[unhead] ${rule.message}${loc}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
key: pluginKey,
|
|
419
|
+
hooks: {
|
|
420
|
+
"ssr:streamChunk": ({ tags }) => {
|
|
421
|
+
const severity = severityFor("streamed-tag-hidden-from-bots", "warn");
|
|
422
|
+
if (severity === "off")
|
|
423
|
+
return;
|
|
424
|
+
const rules = [];
|
|
425
|
+
for (const pending of tags) {
|
|
426
|
+
for (const tag of expandPendingTag(pending)) {
|
|
427
|
+
if (!isHiddenFromBots(tag, !!head._stream?.writesBodyTags))
|
|
428
|
+
continue;
|
|
429
|
+
const entryIndex = tag._p != null ? tag._p >> 10 : void 0;
|
|
430
|
+
rules.push({
|
|
431
|
+
id: "streamed-tag-hidden-from-bots",
|
|
432
|
+
message: `Bots will not see ${describeTag(tag)}. It arrived after the shell, so it ships as a client patch. Register it before the shell.`,
|
|
433
|
+
severity,
|
|
434
|
+
source: entryIndex != null ? stacks.get(entryIndex) : void 0,
|
|
435
|
+
tag
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
dispatch(rules, "append");
|
|
440
|
+
},
|
|
441
|
+
"entries:normalize": ({ entry }) => {
|
|
442
|
+
const input = inputShapeObserver.take();
|
|
443
|
+
if (!input || input.constructor !== Object)
|
|
444
|
+
return;
|
|
445
|
+
const shape = inputShapeFromRuntime("head", input);
|
|
446
|
+
for (const predicate of Object.values(inputShapePredicates)) {
|
|
447
|
+
for (const diagnostic of predicate(shape))
|
|
448
|
+
pendingInputDiagnostics.push({ diagnostic, entryIndex: entry._i });
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
"tags:afterResolve": ({ tags }) => {
|
|
452
|
+
const rules = [];
|
|
453
|
+
function report(id, message, defaultSeverity, tag, inputEntryIndex) {
|
|
454
|
+
const severity = severityFor(id, defaultSeverity);
|
|
455
|
+
if (severity === "off")
|
|
456
|
+
return;
|
|
457
|
+
const entryIndex = inputEntryIndex ?? (tag?._p != null ? tag._p >> 10 : void 0);
|
|
458
|
+
const source = entryIndex != null ? stacks.get(entryIndex) : void 0;
|
|
459
|
+
rules.push({ id, message, severity, source, tag });
|
|
460
|
+
}
|
|
461
|
+
for (const { diagnostic, entryIndex } of pendingInputDiagnostics) {
|
|
462
|
+
const severity = PREDICATE_SEVERITY[diagnostic.ruleId] ?? "warn";
|
|
463
|
+
report(diagnostic.ruleId, diagnostic.message, severity, void 0, entryIndex);
|
|
464
|
+
}
|
|
465
|
+
pendingInputDiagnostics.length = 0;
|
|
466
|
+
const metaByKey = /* @__PURE__ */ new Map();
|
|
467
|
+
let canonicalHref;
|
|
468
|
+
let hasOgTags = false;
|
|
469
|
+
let hasTitle = false;
|
|
470
|
+
let hasDescription = false;
|
|
471
|
+
let isIndexable = true;
|
|
472
|
+
for (const tag of tags) {
|
|
473
|
+
if (tag.tag === "title")
|
|
474
|
+
hasTitle = true;
|
|
475
|
+
if (tag.tag === "meta") {
|
|
476
|
+
const key = tag.props.property || (tag.props.name ? String(tag.props.name).toLowerCase() : void 0);
|
|
477
|
+
if (key) {
|
|
478
|
+
metaByKey.set(key, tag);
|
|
479
|
+
if (key.startsWith("og:"))
|
|
480
|
+
hasOgTags = true;
|
|
481
|
+
if (key === "description")
|
|
482
|
+
hasDescription = true;
|
|
483
|
+
if (key === "robots" && String(tag.props.content ?? "").toLowerCase().includes("noindex"))
|
|
484
|
+
isIndexable = false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (tag.tag === "link" && tag.props.rel === "canonical")
|
|
488
|
+
canonicalHref = tag.props.href;
|
|
489
|
+
}
|
|
490
|
+
function emitFromPredicates(diagnostics, tag) {
|
|
491
|
+
for (const diag of diagnostics) {
|
|
492
|
+
const sev = PREDICATE_SEVERITY[diag.ruleId] ?? "warn";
|
|
493
|
+
report(diag.ruleId, diag.message, sev, tag);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
for (const tag of tags) {
|
|
497
|
+
const { props } = tag;
|
|
498
|
+
const metaKey = props.property || (props.name ? String(props.name).toLowerCase() : void 0);
|
|
499
|
+
const tagInput = tagInputFromRuntime(tag);
|
|
500
|
+
if (tagInput) {
|
|
501
|
+
for (const predicate of Object.values(tagPredicates))
|
|
502
|
+
emitFromPredicates(predicate(tagInput), tag);
|
|
503
|
+
}
|
|
504
|
+
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
505
|
+
const inputShape = inputShapeFromRuntime(tag.tag, tag.props);
|
|
506
|
+
for (const predicate of Object.values(inputShapePredicates))
|
|
507
|
+
emitFromPredicates(predicate(inputShape), tag);
|
|
508
|
+
}
|
|
509
|
+
if (tag.tag === "meta" && typeof metaKey === "string") {
|
|
510
|
+
const normalizedMetaKey = metaKey.toLowerCase();
|
|
511
|
+
if (normalizedMetaKey.startsWith("twitter:") && !SLACK_TWITTER_META_NAMES.has(normalizedMetaKey))
|
|
512
|
+
report("deprecated-twitter-meta", `${normalizedMetaKey} is deprecated. Use Open Graph metadata instead.`, "warn", tag);
|
|
513
|
+
}
|
|
514
|
+
if (tag.tag === "meta" && metaKey && URL_META_KEYS.has(metaKey)) {
|
|
515
|
+
const content = String(props.content ?? "");
|
|
516
|
+
if (content && !isAbsoluteUrl(content))
|
|
517
|
+
report("non-absolute-og-url", `${metaKey} should be an absolute URL, received "${content}".`, "warn", tag);
|
|
518
|
+
}
|
|
519
|
+
if (tag.tag === "meta" && metaKey) {
|
|
520
|
+
const content = String(props.content ?? "");
|
|
521
|
+
if (content && TEMPLATE_PARAM_RE.test(content))
|
|
522
|
+
report("unresolved-template-param", `Unresolved template param in ${metaKey}: "${content}".`, "warn", tag);
|
|
523
|
+
}
|
|
524
|
+
if (tag.tag === "title") {
|
|
525
|
+
const titleInput = titleInputFromRuntime(tag);
|
|
526
|
+
if (titleInput) {
|
|
527
|
+
for (const diag of headInputPredicates["no-html-in-title"](titleInput))
|
|
528
|
+
report(diag.ruleId, diag.message, "warn", tag);
|
|
529
|
+
}
|
|
530
|
+
const text = String(tag.textContent ?? "");
|
|
531
|
+
if (TEMPLATE_PARAM_RE.test(text))
|
|
532
|
+
report("unresolved-template-param", `Unresolved template param in title: "${text}".`, "warn", tag);
|
|
533
|
+
if (!text.trim())
|
|
534
|
+
report("empty-title", `Title tag is empty. If using titleTemplate, ensure it produces output.`, "warn", tag);
|
|
535
|
+
}
|
|
536
|
+
if (tag.tag === "script" && props.src && !props.async && !props.defer && props.type !== "module" && (!tag.tagPosition || tag.tagPosition === "head"))
|
|
537
|
+
report("render-blocking-script", `Script "${props.src}" is render-blocking. Add "async", "defer", or use type="module" to avoid blocking the critical rendering path.`, "warn", tag);
|
|
538
|
+
if (tag.tag === "link" && props.rel === "preload" && props.fetchpriority === "low" && props.as !== "script")
|
|
539
|
+
report("preload-fetchpriority-conflict", `Preload with fetchpriority="low" is contradictory \u2014 preload signals critical, low priority contradicts that.`, "warn", tag);
|
|
540
|
+
if (tag.tag === "style" && (tag.innerHTML || tag.textContent)) {
|
|
541
|
+
const content = tag.innerHTML || tag.textContent || "";
|
|
542
|
+
const sizeKB = new TextEncoder().encode(content).byteLength / 1024;
|
|
543
|
+
const { maxKB: styleMaxKB } = resolveOptions(ruleConfig, "inline-style-size", { maxKB: 14 });
|
|
544
|
+
if (sizeKB > styleMaxKB)
|
|
545
|
+
report("inline-style-size", `Inline <style> is ${sizeKB.toFixed(1)}KB \u2014 exceeds ${styleMaxKB}KB critical CSS budget. Consider moving to an external stylesheet for cacheability.`, "info", tag);
|
|
546
|
+
}
|
|
547
|
+
if (tag.tag === "script" && !props.src && (tag.innerHTML || tag.textContent)) {
|
|
548
|
+
const content = tag.innerHTML || tag.textContent || "";
|
|
549
|
+
const sizeKB = new TextEncoder().encode(content).byteLength / 1024;
|
|
550
|
+
const { maxKB: scriptMaxKB } = resolveOptions(ruleConfig, "inline-script-size", { maxKB: 2 });
|
|
551
|
+
if (sizeKB > scriptMaxKB)
|
|
552
|
+
report("inline-script-size", `Inline <script> is ${sizeKB.toFixed(1)}KB \u2014 consider moving to an external file for cacheability.`, "info", tag);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const ogUrl = metaByKey.get("og:url");
|
|
556
|
+
if (canonicalHref && ogUrl?.props.content && canonicalHref !== ogUrl.props.content)
|
|
557
|
+
report("canonical-og-url-mismatch", `Canonical URL "${canonicalHref}" differs from og:url "${ogUrl.props.content}".`, "warn", ogUrl);
|
|
558
|
+
if (metaByKey.has("og:image") && (!metaByKey.has("og:image:width") || !metaByKey.has("og:image:height")))
|
|
559
|
+
report("og-image-missing-dimensions", `og:image is set but og:image:width and/or og:image:height are missing \u2014 social platforms may not display the image.`, "warn", metaByKey.get("og:image"));
|
|
560
|
+
if (hasOgTags) {
|
|
561
|
+
if (!metaByKey.has("og:title"))
|
|
562
|
+
report("og-missing-title", `Open Graph tags are present but og:title is missing.`, "warn");
|
|
563
|
+
if (!metaByKey.has("og:description"))
|
|
564
|
+
report("og-missing-description", `Open Graph tags are present but og:description is missing.`, "warn");
|
|
565
|
+
}
|
|
566
|
+
if (!hasTitle)
|
|
567
|
+
report("missing-title", `Page is missing a <title> tag.`, "warn");
|
|
568
|
+
if (!hasDescription && isIndexable)
|
|
569
|
+
report("missing-description", `Page is missing a meta description and is indexable by search engines.`, "warn");
|
|
570
|
+
const { max: maxPreloads } = resolveOptions(ruleConfig, "too-many-preloads", { max: 6 });
|
|
571
|
+
const preloadCount = tags.filter((t) => t.tag === "link" && t.props.rel === "preload").length;
|
|
572
|
+
if (preloadCount > maxPreloads)
|
|
573
|
+
report("too-many-preloads", `Found ${preloadCount} preload links \u2014 more than ${maxPreloads} preloads compete for bandwidth and can hurt performance.`, "warn");
|
|
574
|
+
const { max: maxPrefetches } = resolveOptions(ruleConfig, "too-many-prefetches", { max: 50 });
|
|
575
|
+
const prefetchCount = tags.filter((t) => t.tag === "link" && t.props.rel === "prefetch").length;
|
|
576
|
+
if (prefetchCount > maxPrefetches)
|
|
577
|
+
report("too-many-prefetches", `Found ${prefetchCount} prefetch links. The configured maximum of ${maxPrefetches} is an advisory guardrail, not a standards limit. Large speculative fetch sets can consume bandwidth and cache capacity.`, "info");
|
|
578
|
+
const { max: maxPreconnects } = resolveOptions(ruleConfig, "too-many-preconnects", { max: 4 });
|
|
579
|
+
const preconnectCount = tags.filter((t) => t.tag === "link" && t.props.rel === "preconnect").length;
|
|
580
|
+
if (preconnectCount > maxPreconnects)
|
|
581
|
+
report("too-many-preconnects", `Found ${preconnectCount} preconnect links \u2014 each initiates a TCP+TLS handshake, more than ${maxPreconnects} compete for limited connections.`, "warn");
|
|
582
|
+
const preconnectOrigins = /* @__PURE__ */ new Set();
|
|
583
|
+
const dnsPrefetchTags = [];
|
|
584
|
+
for (const tag of tags) {
|
|
585
|
+
if (tag.tag === "link" && tag.props.href) {
|
|
586
|
+
if (tag.props.rel === "preconnect")
|
|
587
|
+
preconnectOrigins.add(tag.props.href);
|
|
588
|
+
else if (tag.props.rel === "dns-prefetch")
|
|
589
|
+
dnsPrefetchTags.push(tag);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
for (const tag of dnsPrefetchTags) {
|
|
593
|
+
if (preconnectOrigins.has(tag.props.href))
|
|
594
|
+
report("redundant-dns-prefetch", `dns-prefetch for "${tag.props.href}" is redundant \u2014 preconnect already includes DNS resolution.`, "info", tag);
|
|
595
|
+
}
|
|
596
|
+
const preloadScriptHrefs = /* @__PURE__ */ new Map();
|
|
597
|
+
for (const tag of tags) {
|
|
598
|
+
if (tag.tag === "link" && tag.props.rel === "preload" && tag.props.as === "script" && tag.props.href && tag.props.fetchpriority !== "low")
|
|
599
|
+
preloadScriptHrefs.set(tag.props.href, tag);
|
|
600
|
+
}
|
|
601
|
+
for (const tag of tags) {
|
|
602
|
+
if (tag.tag === "script" && tag.props.src && (tag.props.async || tag.props.defer)) {
|
|
603
|
+
const preloadTag = preloadScriptHrefs.get(tag.props.src);
|
|
604
|
+
if (preloadTag) {
|
|
605
|
+
const attr = tag.props.async ? "async" : "defer";
|
|
606
|
+
report("preload-async-defer-conflict", `Script "${tag.props.src}" is preloaded but has "${attr}" \u2014 preload escalates priority, defeating the purpose of ${attr}. Remove the preload or add fetchpriority="low" to the script.`, "warn", preloadTag);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const preloadHrefs = /* @__PURE__ */ new Set();
|
|
611
|
+
for (const tag of tags) {
|
|
612
|
+
if (tag.tag === "link" && tag.props.rel === "preload" && tag.props.href)
|
|
613
|
+
preloadHrefs.add(tag.props.href);
|
|
614
|
+
}
|
|
615
|
+
for (const tag of tags) {
|
|
616
|
+
if (tag.tag === "link" && tag.props.rel === "prefetch" && tag.props.href && preloadHrefs.has(tag.props.href))
|
|
617
|
+
report("prefetch-preload-conflict", `"${tag.props.href}" has both preload and prefetch \u2014 use preload for current page resources, prefetch for future navigation.`, "warn", tag);
|
|
618
|
+
}
|
|
619
|
+
if (!head.plugins.has("template-params")) {
|
|
620
|
+
const tpTag = tags.find((t) => t.tag === "templateParams");
|
|
621
|
+
if (tpTag)
|
|
622
|
+
report("missing-template-params-plugin", `templateParams are set but TemplateParamsPlugin is not registered. In v3, this plugin is opt-in. Add it to createHead({ plugins: [TemplateParamsPlugin] }).`, "warn", tpTag);
|
|
623
|
+
}
|
|
624
|
+
if (!head.plugins.has("aliasSorting")) {
|
|
625
|
+
for (const tag of tags) {
|
|
626
|
+
const p = typeof tag.tagPriority === "string" ? tag.tagPriority : "";
|
|
627
|
+
if (p.startsWith("before:") || p.startsWith("after:")) {
|
|
628
|
+
report("missing-alias-sorting-plugin", `Tag priority alias "${p}" requires AliasSortingPlugin. In v3, this plugin is opt-in. Add it to createHead({ plugins: [AliasSortingPlugin] }).`, "warn", tag);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const { max: maxHighPriority } = resolveOptions(ruleConfig, "too-many-fetchpriority-high", { max: 2 });
|
|
634
|
+
const highPriorityCount = tags.filter((t) => t.props.fetchpriority === "high").length;
|
|
635
|
+
if (highPriorityCount > maxHighPriority)
|
|
636
|
+
report("too-many-fetchpriority-high", `Found ${highPriorityCount} resources with fetchpriority="high". When everything is high priority, nothing is. Limit to ${maxHighPriority} for the signal to be effective.`, "warn");
|
|
637
|
+
const resourceHintsSeen = /* @__PURE__ */ new Map();
|
|
638
|
+
for (const tag of tags) {
|
|
639
|
+
if (tag.tag === "link" && tag.props.href && (tag.props.rel === "preload" || tag.props.rel === "prefetch" || tag.props.rel === "preconnect")) {
|
|
640
|
+
const crossoriginSuffix = tag.props.rel === "preconnect" && "crossorigin" in tag.props ? ":cors" : "";
|
|
641
|
+
const key = `${tag.props.rel}:${tag.props.href}${crossoriginSuffix}`;
|
|
642
|
+
if (resourceHintsSeen.has(key))
|
|
643
|
+
report("duplicate-resource-hint", `Duplicate ${tag.props.rel} for "${tag.props.href}".`, "warn", tag);
|
|
644
|
+
else
|
|
645
|
+
resourceHintsSeen.set(key, tag);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (head.ssr) {
|
|
649
|
+
const { maxPosition: charsetMaxPos } = resolveOptions(ruleConfig, "charset-not-early", { maxPosition: 3 });
|
|
650
|
+
const headElementTags = /* @__PURE__ */ new Set(["title", "base", "meta", "link", "style", "script", "noscript"]);
|
|
651
|
+
const sortedHeadTags = tags.filter((t) => headElementTags.has(t.tag) && (!t.tagPosition || t.tagPosition === "head")).sort((a, b) => (a._w ?? 100) === (b._w ?? 100) ? (a._p ?? 0) - (b._p ?? 0) : (a._w ?? 100) - (b._w ?? 100));
|
|
652
|
+
let charsetTag;
|
|
653
|
+
let charsetPosition = -1;
|
|
654
|
+
for (let i = 0; i < sortedHeadTags.length; i++) {
|
|
655
|
+
const tag = sortedHeadTags[i];
|
|
656
|
+
if (tag.tag === "meta" && ("charset" in tag.props || tag.props["http-equiv"]?.toLowerCase() === "content-type")) {
|
|
657
|
+
charsetTag = tag;
|
|
658
|
+
charsetPosition = i + 1;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (charsetTag && charsetPosition > charsetMaxPos)
|
|
663
|
+
report("charset-not-early", `<meta charset> is at position ${charsetPosition} in <head>. It should be within the first ${charsetMaxPos} tags so the browser doesn't need to re-parse.`, "warn", charsetTag);
|
|
664
|
+
}
|
|
665
|
+
const moduleScriptSrcs = /* @__PURE__ */ new Set();
|
|
666
|
+
for (const tag of tags) {
|
|
667
|
+
if (tag.tag === "script" && tag.props.type === "module" && tag.props.src)
|
|
668
|
+
moduleScriptSrcs.add(tag.props.src);
|
|
669
|
+
}
|
|
670
|
+
for (const tag of tags) {
|
|
671
|
+
if (tag.tag === "link" && tag.props.rel === "preload" && tag.props.as === "script" && tag.props.href && moduleScriptSrcs.has(tag.props.href))
|
|
672
|
+
report("preload-not-modulepreload", `"${tag.props.href}" is a module script but uses rel="preload". Use rel="modulepreload" instead to also trigger module parsing.`, "warn", tag);
|
|
673
|
+
}
|
|
674
|
+
const corsOrigins = /* @__PURE__ */ new Set();
|
|
675
|
+
const preconnectCorsOrigins = /* @__PURE__ */ new Set();
|
|
676
|
+
for (const tag of tags) {
|
|
677
|
+
if (tag.tag === "link" && tag.props.href && "crossorigin" in tag.props) {
|
|
678
|
+
const origin = extractOrigin(tag.props.href);
|
|
679
|
+
if (origin) {
|
|
680
|
+
corsOrigins.add(origin);
|
|
681
|
+
if (tag.props.rel === "preconnect")
|
|
682
|
+
preconnectCorsOrigins.add(origin);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
for (const tag of tags) {
|
|
687
|
+
if (tag.tag === "link" && tag.props.rel === "preconnect" && tag.props.href && !("crossorigin" in tag.props)) {
|
|
688
|
+
const origin = extractOrigin(tag.props.href);
|
|
689
|
+
if (origin && corsOrigins.has(origin) && !preconnectCorsOrigins.has(origin))
|
|
690
|
+
report("preconnect-missing-crossorigin", `Preconnect to "${tag.props.href}" is missing "crossorigin" but CORS resources are loaded from this origin. Without it, the browser opens a separate connection for CORS requests.`, "warn", tag);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const { maxBytes: crawlerMaxBytes } = resolveOptions(ruleConfig, "meta-beyond-1mb", { maxBytes: 1048576 });
|
|
694
|
+
let byteOffset = 0;
|
|
695
|
+
for (const tag of tags) {
|
|
696
|
+
if (tag.tagPosition && tag.tagPosition !== "head")
|
|
697
|
+
continue;
|
|
698
|
+
const props = Object.entries(tag.props).filter(([, v]) => v !== false && v != null).map(([k, v]) => v === true || v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
|
|
699
|
+
const content = tag.innerHTML || tag.textContent || "";
|
|
700
|
+
const tagSize = `<${tag.tag}${props}>${content}</${tag.tag}>
|
|
701
|
+
`.length;
|
|
702
|
+
byteOffset += tagSize;
|
|
703
|
+
if (byteOffset > crawlerMaxBytes && tag.tag === "meta") {
|
|
704
|
+
const key = tag.props.property || tag.props.name || "unknown";
|
|
705
|
+
report(
|
|
706
|
+
"meta-beyond-1mb",
|
|
707
|
+
`Meta tag "${key}" is rendered ~${(byteOffset / 1024).toFixed(0)}KB into <head>, beyond the ${(crawlerMaxBytes / 1048576).toFixed(0)}MB crawler parsing limit. Social crawlers (Facebook, Twitter) may not see it. Use \`tagPriority\` to promote it, or configure a custom \`tagWeight\` to reorder tags for bot requests.`,
|
|
708
|
+
"warn",
|
|
709
|
+
tag
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
dispatch(rules, "replace");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
}, pluginKey);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export { CanonicalPlugin, InferSeoMetaPlugin, MinifyPlugin, ValidatePlugin, defineHeadPlugin };
|