@flightdev/seo 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.js ADDED
@@ -0,0 +1,1239 @@
1
+ // src/head.ts
2
+ var TAG_ORDER = {
3
+ base: 0,
4
+ charset: 1,
5
+ "http-equiv": 2,
6
+ viewport: 3,
7
+ title: 4,
8
+ meta: 5,
9
+ link: 6,
10
+ script: 7
11
+ };
12
+ var VOID_TAGS = /* @__PURE__ */ new Set(["meta", "link", "base"]);
13
+ var BOOLEAN_ATTRS = /* @__PURE__ */ new Set(["async", "defer", "nomodule", "crossorigin"]);
14
+ function generateTagKey(tag) {
15
+ if (tag.key) {
16
+ return tag.key;
17
+ }
18
+ const attrs = tag.attrs || {};
19
+ switch (tag.tag) {
20
+ case "title":
21
+ return "title";
22
+ case "base":
23
+ return "base";
24
+ case "meta": {
25
+ if (attrs.charset) return "meta:charset";
26
+ if (attrs.name) return `meta:name:${attrs.name}`;
27
+ if (attrs.property) return `meta:property:${attrs.property}`;
28
+ if (attrs["http-equiv"]) return `meta:http-equiv:${attrs["http-equiv"]}`;
29
+ return `meta:${JSON.stringify(attrs)}`;
30
+ }
31
+ case "link": {
32
+ if (attrs.rel === "canonical") return "link:canonical";
33
+ if (attrs.rel === "manifest") return "link:manifest";
34
+ if (attrs.rel && attrs.href) return `link:${attrs.rel}:${attrs.href}`;
35
+ if (attrs.rel && attrs.hreflang) return `link:${attrs.rel}:${attrs.hreflang}`;
36
+ return `link:${JSON.stringify(attrs)}`;
37
+ }
38
+ case "script": {
39
+ if (attrs.type === "application/ld+json" && attrs.id) {
40
+ return `script:json-ld:${attrs.id}`;
41
+ }
42
+ if (attrs.src) return `script:${attrs.src}`;
43
+ return `script:${JSON.stringify(attrs)}`;
44
+ }
45
+ default:
46
+ return `${tag.tag}:${JSON.stringify(attrs)}`;
47
+ }
48
+ }
49
+ function getTagOrder(tag) {
50
+ const attrs = tag.attrs || {};
51
+ if (tag.tag === "meta") {
52
+ if (attrs.charset) return TAG_ORDER.charset;
53
+ if (attrs["http-equiv"]) return TAG_ORDER["http-equiv"];
54
+ if (attrs.name === "viewport") return TAG_ORDER.viewport;
55
+ }
56
+ return TAG_ORDER[tag.tag] ?? 99;
57
+ }
58
+ var HeadManager = class {
59
+ tags = /* @__PURE__ */ new Map();
60
+ frozen = false;
61
+ /**
62
+ * Add a tag to the head
63
+ */
64
+ addTag(tag) {
65
+ if (this.frozen) {
66
+ throw new Error("HeadManager is frozen and cannot be modified");
67
+ }
68
+ const key = generateTagKey(tag);
69
+ this.tags.set(key, { ...tag, key });
70
+ return this;
71
+ }
72
+ /**
73
+ * Add multiple tags
74
+ */
75
+ addTags(tags) {
76
+ for (const tag of tags) {
77
+ this.addTag(tag);
78
+ }
79
+ return this;
80
+ }
81
+ /**
82
+ * Remove a tag by key
83
+ */
84
+ removeTag(key) {
85
+ if (this.frozen) {
86
+ throw new Error("HeadManager is frozen and cannot be modified");
87
+ }
88
+ this.tags.delete(key);
89
+ return this;
90
+ }
91
+ /**
92
+ * Check if a tag exists
93
+ */
94
+ hasTag(key) {
95
+ return this.tags.has(key);
96
+ }
97
+ /**
98
+ * Get a tag by key
99
+ */
100
+ getTag(key) {
101
+ return this.tags.get(key);
102
+ }
103
+ /**
104
+ * Get all tags
105
+ */
106
+ getTags() {
107
+ return Array.from(this.tags.values());
108
+ }
109
+ /**
110
+ * Clear all tags
111
+ */
112
+ clear() {
113
+ if (this.frozen) {
114
+ throw new Error("HeadManager is frozen and cannot be modified");
115
+ }
116
+ this.tags.clear();
117
+ return this;
118
+ }
119
+ /**
120
+ * Freeze the manager (no more modifications)
121
+ */
122
+ freeze() {
123
+ this.frozen = true;
124
+ return this;
125
+ }
126
+ /**
127
+ * Check if frozen
128
+ */
129
+ isFrozen() {
130
+ return this.frozen;
131
+ }
132
+ /**
133
+ * Render all tags to HTML
134
+ */
135
+ render() {
136
+ const tags = this.getTags();
137
+ tags.sort((a, b) => getTagOrder(a) - getTagOrder(b));
138
+ const html = tags.map((tag) => renderTag(tag)).join("\n");
139
+ return { tags, html };
140
+ }
141
+ };
142
+ function escapeHtml(str) {
143
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
144
+ }
145
+ function escapeAttr(str) {
146
+ return str.replace(/"/g, "&quot;");
147
+ }
148
+ function renderTag(tag) {
149
+ const parts = [`<${tag.tag}`];
150
+ if (tag.attrs) {
151
+ for (const [key, value] of Object.entries(tag.attrs)) {
152
+ if (value === void 0 || value === false) {
153
+ continue;
154
+ }
155
+ if (value === true) {
156
+ parts.push(` ${key}`);
157
+ } else if (BOOLEAN_ATTRS.has(key) && value === "") {
158
+ parts.push(` ${key}`);
159
+ } else {
160
+ parts.push(` ${key}="${escapeAttr(String(value))}"`);
161
+ }
162
+ }
163
+ }
164
+ if (VOID_TAGS.has(tag.tag)) {
165
+ parts.push(" />");
166
+ } else if (tag.content !== void 0) {
167
+ const isJsonLd = tag.tag === "script" && tag.attrs?.type === "application/ld+json";
168
+ const content = isJsonLd ? tag.content : escapeHtml(tag.content);
169
+ parts.push(`>${content}</${tag.tag}>`);
170
+ } else {
171
+ parts.push(`></${tag.tag}>`);
172
+ }
173
+ return parts.join("");
174
+ }
175
+ function renderTags(tags) {
176
+ return tags.map((tag) => renderTag(tag)).join("\n");
177
+ }
178
+ function createHeadManager() {
179
+ return new HeadManager();
180
+ }
181
+ var currentHeadContext = null;
182
+ function setHeadContext(context) {
183
+ currentHeadContext = context;
184
+ }
185
+ function getHeadContext() {
186
+ return currentHeadContext;
187
+ }
188
+ function clearHeadContext() {
189
+ currentHeadContext = null;
190
+ }
191
+ function withHeadContext(context, fn) {
192
+ const previous = currentHeadContext;
193
+ currentHeadContext = context;
194
+ try {
195
+ return fn();
196
+ } finally {
197
+ currentHeadContext = previous;
198
+ }
199
+ }
200
+
201
+ // src/meta.ts
202
+ function generateTitleTag(title, template, defaultTitle) {
203
+ let resolvedTitle = title || defaultTitle;
204
+ if (!resolvedTitle) {
205
+ return null;
206
+ }
207
+ if (template && title) {
208
+ resolvedTitle = template.replace("%s", title);
209
+ }
210
+ return {
211
+ tag: "title",
212
+ content: resolvedTitle,
213
+ key: "title"
214
+ };
215
+ }
216
+ function generateCharsetTag(charset = "utf-8") {
217
+ return {
218
+ tag: "meta",
219
+ attrs: { charset },
220
+ key: "meta:charset"
221
+ };
222
+ }
223
+ function generateViewportTag(viewport) {
224
+ if (!viewport) {
225
+ return null;
226
+ }
227
+ let content;
228
+ if (typeof viewport === "string") {
229
+ content = viewport;
230
+ } else {
231
+ const parts = [];
232
+ if (viewport.width !== void 0) {
233
+ parts.push(`width=${viewport.width}`);
234
+ }
235
+ if (viewport.height !== void 0) {
236
+ parts.push(`height=${viewport.height}`);
237
+ }
238
+ if (viewport.initialScale !== void 0) {
239
+ parts.push(`initial-scale=${viewport.initialScale}`);
240
+ }
241
+ if (viewport.minimumScale !== void 0) {
242
+ parts.push(`minimum-scale=${viewport.minimumScale}`);
243
+ }
244
+ if (viewport.maximumScale !== void 0) {
245
+ parts.push(`maximum-scale=${viewport.maximumScale}`);
246
+ }
247
+ if (viewport.userScalable !== void 0) {
248
+ parts.push(`user-scalable=${viewport.userScalable ? "yes" : "no"}`);
249
+ }
250
+ if (viewport.viewportFit !== void 0) {
251
+ parts.push(`viewport-fit=${viewport.viewportFit}`);
252
+ }
253
+ content = parts.join(", ");
254
+ }
255
+ if (!content) {
256
+ return null;
257
+ }
258
+ return {
259
+ tag: "meta",
260
+ attrs: { name: "viewport", content },
261
+ key: "meta:name:viewport"
262
+ };
263
+ }
264
+ function generateDescriptionTag(description) {
265
+ if (!description) {
266
+ return null;
267
+ }
268
+ return {
269
+ tag: "meta",
270
+ attrs: { name: "description", content: description },
271
+ key: "meta:name:description"
272
+ };
273
+ }
274
+ function generateKeywordsTag(keywords) {
275
+ if (!keywords) {
276
+ return null;
277
+ }
278
+ const content = Array.isArray(keywords) ? keywords.join(", ") : keywords;
279
+ return {
280
+ tag: "meta",
281
+ attrs: { name: "keywords", content },
282
+ key: "meta:name:keywords"
283
+ };
284
+ }
285
+ function generateAuthorTag(author) {
286
+ if (!author) {
287
+ return null;
288
+ }
289
+ return {
290
+ tag: "meta",
291
+ attrs: { name: "author", content: author },
292
+ key: "meta:name:author"
293
+ };
294
+ }
295
+ function generateGeneratorTag(generator) {
296
+ if (!generator) {
297
+ return null;
298
+ }
299
+ return {
300
+ tag: "meta",
301
+ attrs: { name: "generator", content: generator },
302
+ key: "meta:name:generator"
303
+ };
304
+ }
305
+ function generateApplicationNameTag(name) {
306
+ if (!name) {
307
+ return null;
308
+ }
309
+ return {
310
+ tag: "meta",
311
+ attrs: { name: "application-name", content: name },
312
+ key: "meta:name:application-name"
313
+ };
314
+ }
315
+ function generateReferrerTag(referrer) {
316
+ if (!referrer) {
317
+ return null;
318
+ }
319
+ return {
320
+ tag: "meta",
321
+ attrs: { name: "referrer", content: referrer },
322
+ key: "meta:name:referrer"
323
+ };
324
+ }
325
+ function generateThemeColorTags(themeColor) {
326
+ if (!themeColor) {
327
+ return [];
328
+ }
329
+ if (typeof themeColor === "string") {
330
+ return [
331
+ {
332
+ tag: "meta",
333
+ attrs: { name: "theme-color", content: themeColor },
334
+ key: "meta:name:theme-color"
335
+ }
336
+ ];
337
+ }
338
+ return themeColor.map((tc, index) => ({
339
+ tag: "meta",
340
+ attrs: {
341
+ name: "theme-color",
342
+ content: tc.color,
343
+ media: tc.media
344
+ },
345
+ key: `meta:name:theme-color:${index}`
346
+ }));
347
+ }
348
+ function generateColorSchemeTag(colorScheme) {
349
+ if (!colorScheme) {
350
+ return null;
351
+ }
352
+ return {
353
+ tag: "meta",
354
+ attrs: { name: "color-scheme", content: colorScheme },
355
+ key: "meta:name:color-scheme"
356
+ };
357
+ }
358
+ function robotsMetaToString(robots) {
359
+ const directives = [];
360
+ if (robots.index === false) {
361
+ directives.push("noindex");
362
+ } else if (robots.index === true) {
363
+ directives.push("index");
364
+ }
365
+ if (robots.follow === false) {
366
+ directives.push("nofollow");
367
+ } else if (robots.follow === true) {
368
+ directives.push("follow");
369
+ }
370
+ if (robots.noarchive) {
371
+ directives.push("noarchive");
372
+ }
373
+ if (robots.nosnippet) {
374
+ directives.push("nosnippet");
375
+ }
376
+ if (robots.notranslate) {
377
+ directives.push("notranslate");
378
+ }
379
+ if (robots.maxSnippet !== void 0) {
380
+ directives.push(`max-snippet:${robots.maxSnippet}`);
381
+ }
382
+ if (robots.maxImagePreview !== void 0) {
383
+ directives.push(`max-image-preview:${robots.maxImagePreview}`);
384
+ }
385
+ if (robots.maxVideoPreview !== void 0) {
386
+ directives.push(`max-video-preview:${robots.maxVideoPreview}`);
387
+ }
388
+ if (robots.unavailableAfter) {
389
+ directives.push(`unavailable_after:${robots.unavailableAfter}`);
390
+ }
391
+ return directives.join(", ");
392
+ }
393
+ function generateRobotsTag(robots) {
394
+ if (!robots) {
395
+ return null;
396
+ }
397
+ const content = typeof robots === "string" ? robots : robotsMetaToString(robots);
398
+ if (!content) {
399
+ return null;
400
+ }
401
+ return {
402
+ tag: "meta",
403
+ attrs: { name: "robots", content },
404
+ key: "meta:name:robots"
405
+ };
406
+ }
407
+ function generateGooglebotTag(googleBot) {
408
+ if (!googleBot) {
409
+ return null;
410
+ }
411
+ const content = typeof googleBot === "string" ? googleBot : robotsMetaToString(googleBot);
412
+ if (!content) {
413
+ return null;
414
+ }
415
+ return {
416
+ tag: "meta",
417
+ attrs: { name: "googlebot", content },
418
+ key: "meta:name:googlebot"
419
+ };
420
+ }
421
+ function generateVerificationTags(verification) {
422
+ if (!verification) {
423
+ return [];
424
+ }
425
+ const tags = [];
426
+ if (verification.google) {
427
+ tags.push({
428
+ tag: "meta",
429
+ attrs: { name: "google-site-verification", content: verification.google },
430
+ key: "meta:name:google-site-verification"
431
+ });
432
+ }
433
+ if (verification.bing) {
434
+ tags.push({
435
+ tag: "meta",
436
+ attrs: { name: "msvalidate.01", content: verification.bing },
437
+ key: "meta:name:msvalidate.01"
438
+ });
439
+ }
440
+ if (verification.yandex) {
441
+ tags.push({
442
+ tag: "meta",
443
+ attrs: { name: "yandex-verification", content: verification.yandex },
444
+ key: "meta:name:yandex-verification"
445
+ });
446
+ }
447
+ if (verification.pinterest) {
448
+ tags.push({
449
+ tag: "meta",
450
+ attrs: { name: "p:domain_verify", content: verification.pinterest },
451
+ key: "meta:name:p:domain_verify"
452
+ });
453
+ }
454
+ if (verification.norton) {
455
+ tags.push({
456
+ tag: "meta",
457
+ attrs: { name: "norton-safeweb-site-verification", content: verification.norton },
458
+ key: "meta:name:norton-safeweb-site-verification"
459
+ });
460
+ }
461
+ return tags;
462
+ }
463
+ function generateCanonicalTag(canonical) {
464
+ if (!canonical) {
465
+ return null;
466
+ }
467
+ return {
468
+ tag: "link",
469
+ attrs: { rel: "canonical", href: canonical },
470
+ key: "link:canonical"
471
+ };
472
+ }
473
+ function generateBaseTag(base) {
474
+ if (!base) {
475
+ return null;
476
+ }
477
+ return {
478
+ tag: "base",
479
+ attrs: { href: base },
480
+ key: "base"
481
+ };
482
+ }
483
+ function generateAlternateTags(alternates) {
484
+ if (!alternates || alternates.length === 0) {
485
+ return [];
486
+ }
487
+ return alternates.map((alt, index) => {
488
+ const attrs = {
489
+ rel: "alternate",
490
+ href: alt.href
491
+ };
492
+ if (alt.hreflang) {
493
+ attrs.hreflang = alt.hreflang;
494
+ }
495
+ if (alt.media) {
496
+ attrs.media = alt.media;
497
+ }
498
+ if (alt.type) {
499
+ attrs.type = alt.type;
500
+ }
501
+ if (alt.title) {
502
+ attrs.title = alt.title;
503
+ }
504
+ return {
505
+ tag: "link",
506
+ attrs,
507
+ key: `link:alternate:${alt.hreflang || index}`
508
+ };
509
+ });
510
+ }
511
+ function generateManifestTag(manifest) {
512
+ if (!manifest) {
513
+ return null;
514
+ }
515
+ return {
516
+ tag: "link",
517
+ attrs: { rel: "manifest", href: manifest },
518
+ key: "link:manifest"
519
+ };
520
+ }
521
+ function generateMetaTags(metadata, options) {
522
+ const tags = [];
523
+ tags.push(generateCharsetTag(metadata.charset));
524
+ const base = generateBaseTag(metadata.base);
525
+ if (base) tags.push(base);
526
+ const viewport = generateViewportTag(metadata.viewport);
527
+ if (viewport) tags.push(viewport);
528
+ const title = generateTitleTag(
529
+ metadata.title,
530
+ metadata.titleTemplate || options?.titleTemplate,
531
+ metadata.defaultTitle || options?.defaultTitle
532
+ );
533
+ if (title) tags.push(title);
534
+ const description = generateDescriptionTag(metadata.description);
535
+ if (description) tags.push(description);
536
+ const keywords = generateKeywordsTag(metadata.keywords);
537
+ if (keywords) tags.push(keywords);
538
+ const author = generateAuthorTag(metadata.author);
539
+ if (author) tags.push(author);
540
+ const generator = generateGeneratorTag(metadata.generator);
541
+ if (generator) tags.push(generator);
542
+ const appName = generateApplicationNameTag(metadata.applicationName);
543
+ if (appName) tags.push(appName);
544
+ const referrer = generateReferrerTag(metadata.referrer);
545
+ if (referrer) tags.push(referrer);
546
+ tags.push(...generateThemeColorTags(metadata.themeColor));
547
+ const colorScheme = generateColorSchemeTag(metadata.colorScheme);
548
+ if (colorScheme) tags.push(colorScheme);
549
+ const robots = generateRobotsTag(metadata.robots);
550
+ if (robots) tags.push(robots);
551
+ const googleBot = generateGooglebotTag(metadata.googleBot);
552
+ if (googleBot) tags.push(googleBot);
553
+ tags.push(...generateVerificationTags(metadata.verification));
554
+ const canonical = generateCanonicalTag(metadata.canonical);
555
+ if (canonical) tags.push(canonical);
556
+ tags.push(...generateAlternateTags(metadata.alternates));
557
+ const manifest = generateManifestTag(metadata.manifest);
558
+ if (manifest) tags.push(manifest);
559
+ if (metadata.other) {
560
+ for (const [name, content] of Object.entries(metadata.other)) {
561
+ tags.push({
562
+ tag: "meta",
563
+ attrs: { name, content },
564
+ key: `meta:name:${name}`
565
+ });
566
+ }
567
+ }
568
+ return tags;
569
+ }
570
+
571
+ // src/opengraph.ts
572
+ function ogTag(property, content) {
573
+ if (content === void 0 || content === null || content === "") {
574
+ return null;
575
+ }
576
+ return {
577
+ tag: "meta",
578
+ attrs: { property: `og:${property}`, content: String(content) },
579
+ key: `meta:property:og:${property}`
580
+ };
581
+ }
582
+ function articleTag(property, content) {
583
+ if (!content) {
584
+ return null;
585
+ }
586
+ return {
587
+ tag: "meta",
588
+ attrs: { property: `article:${property}`, content },
589
+ key: `meta:property:article:${property}`
590
+ };
591
+ }
592
+ function profileTag(property, content) {
593
+ if (!content) {
594
+ return null;
595
+ }
596
+ return {
597
+ tag: "meta",
598
+ attrs: { property: `profile:${property}`, content },
599
+ key: `meta:property:profile:${property}`
600
+ };
601
+ }
602
+ function bookTag(property, content) {
603
+ if (!content) {
604
+ return null;
605
+ }
606
+ return {
607
+ tag: "meta",
608
+ attrs: { property: `book:${property}`, content },
609
+ key: `meta:property:book:${property}`
610
+ };
611
+ }
612
+ function productTag(property, content) {
613
+ if (content === void 0 || content === null || content === "") {
614
+ return null;
615
+ }
616
+ return {
617
+ tag: "meta",
618
+ attrs: { property: `product:${property}`, content: String(content) },
619
+ key: `meta:property:product:${property}`
620
+ };
621
+ }
622
+ function generateImageTags(images) {
623
+ if (!images || images.length === 0) {
624
+ return [];
625
+ }
626
+ const tags = [];
627
+ images.forEach((image, index) => {
628
+ const suffix = index > 0 ? `:${index}` : "";
629
+ tags.push({
630
+ tag: "meta",
631
+ attrs: { property: "og:image", content: image.url },
632
+ key: `meta:property:og:image${suffix}`
633
+ });
634
+ if (image.secureUrl) {
635
+ tags.push({
636
+ tag: "meta",
637
+ attrs: { property: "og:image:secure_url", content: image.secureUrl },
638
+ key: `meta:property:og:image:secure_url${suffix}`
639
+ });
640
+ }
641
+ if (image.type) {
642
+ tags.push({
643
+ tag: "meta",
644
+ attrs: { property: "og:image:type", content: image.type },
645
+ key: `meta:property:og:image:type${suffix}`
646
+ });
647
+ }
648
+ if (image.width) {
649
+ tags.push({
650
+ tag: "meta",
651
+ attrs: { property: "og:image:width", content: String(image.width) },
652
+ key: `meta:property:og:image:width${suffix}`
653
+ });
654
+ }
655
+ if (image.height) {
656
+ tags.push({
657
+ tag: "meta",
658
+ attrs: { property: "og:image:height", content: String(image.height) },
659
+ key: `meta:property:og:image:height${suffix}`
660
+ });
661
+ }
662
+ if (image.alt) {
663
+ tags.push({
664
+ tag: "meta",
665
+ attrs: { property: "og:image:alt", content: image.alt },
666
+ key: `meta:property:og:image:alt${suffix}`
667
+ });
668
+ }
669
+ });
670
+ return tags;
671
+ }
672
+ function generateVideoTags(videos) {
673
+ if (!videos || videos.length === 0) {
674
+ return [];
675
+ }
676
+ const tags = [];
677
+ videos.forEach((video, index) => {
678
+ const suffix = index > 0 ? `:${index}` : "";
679
+ tags.push({
680
+ tag: "meta",
681
+ attrs: { property: "og:video", content: video.url },
682
+ key: `meta:property:og:video${suffix}`
683
+ });
684
+ if (video.secureUrl) {
685
+ tags.push({
686
+ tag: "meta",
687
+ attrs: { property: "og:video:secure_url", content: video.secureUrl },
688
+ key: `meta:property:og:video:secure_url${suffix}`
689
+ });
690
+ }
691
+ if (video.type) {
692
+ tags.push({
693
+ tag: "meta",
694
+ attrs: { property: "og:video:type", content: video.type },
695
+ key: `meta:property:og:video:type${suffix}`
696
+ });
697
+ }
698
+ if (video.width) {
699
+ tags.push({
700
+ tag: "meta",
701
+ attrs: { property: "og:video:width", content: String(video.width) },
702
+ key: `meta:property:og:video:width${suffix}`
703
+ });
704
+ }
705
+ if (video.height) {
706
+ tags.push({
707
+ tag: "meta",
708
+ attrs: { property: "og:video:height", content: String(video.height) },
709
+ key: `meta:property:og:video:height${suffix}`
710
+ });
711
+ }
712
+ });
713
+ return tags;
714
+ }
715
+ function generateAudioTags(audio) {
716
+ if (!audio || audio.length === 0) {
717
+ return [];
718
+ }
719
+ const tags = [];
720
+ audio.forEach((a, index) => {
721
+ const suffix = index > 0 ? `:${index}` : "";
722
+ tags.push({
723
+ tag: "meta",
724
+ attrs: { property: "og:audio", content: a.url },
725
+ key: `meta:property:og:audio${suffix}`
726
+ });
727
+ if (a.secureUrl) {
728
+ tags.push({
729
+ tag: "meta",
730
+ attrs: { property: "og:audio:secure_url", content: a.secureUrl },
731
+ key: `meta:property:og:audio:secure_url${suffix}`
732
+ });
733
+ }
734
+ if (a.type) {
735
+ tags.push({
736
+ tag: "meta",
737
+ attrs: { property: "og:audio:type", content: a.type },
738
+ key: `meta:property:og:audio:type${suffix}`
739
+ });
740
+ }
741
+ });
742
+ return tags;
743
+ }
744
+ function generateArticleTags(article) {
745
+ if (!article) {
746
+ return [];
747
+ }
748
+ const tags = [];
749
+ const publishedTime = articleTag("published_time", article.publishedTime);
750
+ if (publishedTime) tags.push(publishedTime);
751
+ const modifiedTime = articleTag("modified_time", article.modifiedTime);
752
+ if (modifiedTime) tags.push(modifiedTime);
753
+ const expirationTime = articleTag("expiration_time", article.expirationTime);
754
+ if (expirationTime) tags.push(expirationTime);
755
+ const section = articleTag("section", article.section);
756
+ if (section) tags.push(section);
757
+ if (article.authors) {
758
+ article.authors.forEach((author, index) => {
759
+ tags.push({
760
+ tag: "meta",
761
+ attrs: { property: "article:author", content: author },
762
+ key: `meta:property:article:author:${index}`
763
+ });
764
+ });
765
+ }
766
+ if (article.tags) {
767
+ article.tags.forEach((tag, index) => {
768
+ tags.push({
769
+ tag: "meta",
770
+ attrs: { property: "article:tag", content: tag },
771
+ key: `meta:property:article:tag:${index}`
772
+ });
773
+ });
774
+ }
775
+ return tags;
776
+ }
777
+ function generateProfileTags(profile) {
778
+ if (!profile) {
779
+ return [];
780
+ }
781
+ const tags = [];
782
+ const firstName = profileTag("first_name", profile.firstName);
783
+ if (firstName) tags.push(firstName);
784
+ const lastName = profileTag("last_name", profile.lastName);
785
+ if (lastName) tags.push(lastName);
786
+ const username = profileTag("username", profile.username);
787
+ if (username) tags.push(username);
788
+ const gender = profileTag("gender", profile.gender);
789
+ if (gender) tags.push(gender);
790
+ return tags;
791
+ }
792
+ function generateBookTags(book) {
793
+ if (!book) {
794
+ return [];
795
+ }
796
+ const tags = [];
797
+ if (book.authors) {
798
+ book.authors.forEach((author, index) => {
799
+ tags.push({
800
+ tag: "meta",
801
+ attrs: { property: "book:author", content: author },
802
+ key: `meta:property:book:author:${index}`
803
+ });
804
+ });
805
+ }
806
+ const isbn = bookTag("isbn", book.isbn);
807
+ if (isbn) tags.push(isbn);
808
+ const releaseDate = bookTag("release_date", book.releaseDate);
809
+ if (releaseDate) tags.push(releaseDate);
810
+ if (book.tags) {
811
+ book.tags.forEach((tag, index) => {
812
+ tags.push({
813
+ tag: "meta",
814
+ attrs: { property: "book:tag", content: tag },
815
+ key: `meta:property:book:tag:${index}`
816
+ });
817
+ });
818
+ }
819
+ return tags;
820
+ }
821
+ function generateProductTags(product) {
822
+ if (!product) {
823
+ return [];
824
+ }
825
+ const tags = [];
826
+ const price = productTag("price:amount", product.priceAmount);
827
+ if (price) tags.push(price);
828
+ const currency = productTag("price:currency", product.priceCurrency);
829
+ if (currency) tags.push(currency);
830
+ const availability = productTag("availability", product.availability);
831
+ if (availability) tags.push(availability);
832
+ const condition = productTag("condition", product.condition);
833
+ if (condition) tags.push(condition);
834
+ const retailerId = productTag("retailer_item_id", product.retailerItemId);
835
+ if (retailerId) tags.push(retailerId);
836
+ return tags;
837
+ }
838
+ function generateOpenGraphTags(og) {
839
+ if (!og) {
840
+ return [];
841
+ }
842
+ const tags = [];
843
+ const type = ogTag("type", og.type || "website");
844
+ if (type) tags.push(type);
845
+ const title = ogTag("title", og.title);
846
+ if (title) tags.push(title);
847
+ const description = ogTag("description", og.description);
848
+ if (description) tags.push(description);
849
+ const url = ogTag("url", og.url);
850
+ if (url) tags.push(url);
851
+ const siteName = ogTag("site_name", og.siteName);
852
+ if (siteName) tags.push(siteName);
853
+ const locale = ogTag("locale", og.locale);
854
+ if (locale) tags.push(locale);
855
+ if (og.alternateLocales) {
856
+ og.alternateLocales.forEach((alternateLocale, index) => {
857
+ tags.push({
858
+ tag: "meta",
859
+ attrs: { property: "og:locale:alternate", content: alternateLocale },
860
+ key: `meta:property:og:locale:alternate:${index}`
861
+ });
862
+ });
863
+ }
864
+ const determiner = ogTag("determiner", og.determiner);
865
+ if (determiner) tags.push(determiner);
866
+ tags.push(...generateImageTags(og.images));
867
+ tags.push(...generateVideoTags(og.videos));
868
+ tags.push(...generateAudioTags(og.audio));
869
+ tags.push(...generateArticleTags(og.article));
870
+ tags.push(...generateProfileTags(og.profile));
871
+ tags.push(...generateBookTags(og.book));
872
+ tags.push(...generateProductTags(og.product));
873
+ return tags;
874
+ }
875
+ function generateOpenGraphFallback(title, description, url) {
876
+ const tags = [];
877
+ const typeTag = ogTag("type", "website");
878
+ if (typeTag) tags.push(typeTag);
879
+ const titleTag = ogTag("title", title);
880
+ if (titleTag) tags.push(titleTag);
881
+ const descTag = ogTag("description", description);
882
+ if (descTag) tags.push(descTag);
883
+ const urlTag = ogTag("url", url);
884
+ if (urlTag) tags.push(urlTag);
885
+ return tags;
886
+ }
887
+
888
+ // src/twitter.ts
889
+ function twitterTag(name, content) {
890
+ if (!content) {
891
+ return null;
892
+ }
893
+ return {
894
+ tag: "meta",
895
+ attrs: { name: `twitter:${name}`, content },
896
+ key: `meta:name:twitter:${name}`
897
+ };
898
+ }
899
+ function normalizeImage(image) {
900
+ if (!image) {
901
+ return void 0;
902
+ }
903
+ if (typeof image === "string") {
904
+ return { url: image };
905
+ }
906
+ return image;
907
+ }
908
+ function generateTwitterTags(twitter) {
909
+ if (!twitter) {
910
+ return [];
911
+ }
912
+ const tags = [];
913
+ const card = twitterTag("card", twitter.card || "summary");
914
+ if (card) tags.push(card);
915
+ const title = twitterTag("title", twitter.title);
916
+ if (title) tags.push(title);
917
+ const description = twitterTag("description", twitter.description);
918
+ if (description) tags.push(description);
919
+ const site = twitterTag("site", twitter.site);
920
+ if (site) tags.push(site);
921
+ const siteId = twitterTag("site:id", twitter.siteId);
922
+ if (siteId) tags.push(siteId);
923
+ const creator = twitterTag("creator", twitter.creator);
924
+ if (creator) tags.push(creator);
925
+ const creatorId = twitterTag("creator:id", twitter.creatorId);
926
+ if (creatorId) tags.push(creatorId);
927
+ const image = normalizeImage(twitter.image);
928
+ if (image) {
929
+ const imageTag = twitterTag("image", image.url);
930
+ if (imageTag) tags.push(imageTag);
931
+ if (image.alt) {
932
+ const imageAlt = twitterTag("image:alt", image.alt);
933
+ if (imageAlt) tags.push(imageAlt);
934
+ }
935
+ }
936
+ if (twitter.images && twitter.images.length > 0) {
937
+ twitter.images.forEach((img, index) => {
938
+ if (index >= 4) return;
939
+ const normalizedImg = typeof img === "string" ? { url: img } : img;
940
+ tags.push({
941
+ tag: "meta",
942
+ attrs: { name: `twitter:image${index}`, content: normalizedImg.url },
943
+ key: `meta:name:twitter:image${index}`
944
+ });
945
+ if (normalizedImg.alt) {
946
+ tags.push({
947
+ tag: "meta",
948
+ attrs: { name: `twitter:image${index}:alt`, content: normalizedImg.alt },
949
+ key: `meta:name:twitter:image${index}:alt`
950
+ });
951
+ }
952
+ });
953
+ }
954
+ if (twitter.player) {
955
+ const playerUrl = twitterTag("player", twitter.player.url);
956
+ if (playerUrl) tags.push(playerUrl);
957
+ const playerWidth = twitterTag("player:width", String(twitter.player.width));
958
+ if (playerWidth) tags.push(playerWidth);
959
+ const playerHeight = twitterTag("player:height", String(twitter.player.height));
960
+ if (playerHeight) tags.push(playerHeight);
961
+ if (twitter.player.stream) {
962
+ const stream = twitterTag("player:stream", twitter.player.stream);
963
+ if (stream) tags.push(stream);
964
+ }
965
+ }
966
+ if (twitter.app) {
967
+ const appName = twitterTag("app:name:iphone", twitter.app.name);
968
+ if (appName) tags.push(appName);
969
+ tags.push({
970
+ tag: "meta",
971
+ attrs: { name: "twitter:app:name:ipad", content: twitter.app.name },
972
+ key: "meta:name:twitter:app:name:ipad"
973
+ });
974
+ tags.push({
975
+ tag: "meta",
976
+ attrs: { name: "twitter:app:name:googleplay", content: twitter.app.name },
977
+ key: "meta:name:twitter:app:name:googleplay"
978
+ });
979
+ if (twitter.app.id.iphone) {
980
+ const iphoneId = twitterTag("app:id:iphone", twitter.app.id.iphone);
981
+ if (iphoneId) tags.push(iphoneId);
982
+ }
983
+ if (twitter.app.id.ipad) {
984
+ const ipadId = twitterTag("app:id:ipad", twitter.app.id.ipad);
985
+ if (ipadId) tags.push(ipadId);
986
+ }
987
+ if (twitter.app.id.googleplay) {
988
+ const googleId = twitterTag("app:id:googleplay", twitter.app.id.googleplay);
989
+ if (googleId) tags.push(googleId);
990
+ }
991
+ if (twitter.app.url) {
992
+ if (twitter.app.url.iphone) {
993
+ const iphoneUrl = twitterTag("app:url:iphone", twitter.app.url.iphone);
994
+ if (iphoneUrl) tags.push(iphoneUrl);
995
+ }
996
+ if (twitter.app.url.ipad) {
997
+ const ipadUrl = twitterTag("app:url:ipad", twitter.app.url.ipad);
998
+ if (ipadUrl) tags.push(ipadUrl);
999
+ }
1000
+ if (twitter.app.url.googleplay) {
1001
+ const googleUrl = twitterTag("app:url:googleplay", twitter.app.url.googleplay);
1002
+ if (googleUrl) tags.push(googleUrl);
1003
+ }
1004
+ }
1005
+ }
1006
+ if (twitter.label1) {
1007
+ const label1 = twitterTag("label1", twitter.label1);
1008
+ if (label1) tags.push(label1);
1009
+ }
1010
+ if (twitter.data1) {
1011
+ const data1 = twitterTag("data1", twitter.data1);
1012
+ if (data1) tags.push(data1);
1013
+ }
1014
+ if (twitter.label2) {
1015
+ const label2 = twitterTag("label2", twitter.label2);
1016
+ if (label2) tags.push(label2);
1017
+ }
1018
+ if (twitter.data2) {
1019
+ const data2 = twitterTag("data2", twitter.data2);
1020
+ if (data2) tags.push(data2);
1021
+ }
1022
+ return tags;
1023
+ }
1024
+ function generateTwitterFallback(title, description, image, cardType = "summary") {
1025
+ const tags = [];
1026
+ const card = twitterTag("card", cardType);
1027
+ if (card) tags.push(card);
1028
+ const titleTag = twitterTag("title", title);
1029
+ if (titleTag) tags.push(titleTag);
1030
+ const descTag = twitterTag("description", description);
1031
+ if (descTag) tags.push(descTag);
1032
+ const imageTag = twitterTag("image", image);
1033
+ if (imageTag) tags.push(imageTag);
1034
+ return tags;
1035
+ }
1036
+
1037
+ // src/index.ts
1038
+ function createSEO(config = {}) {
1039
+ const fullConfig = {
1040
+ baseUrl: config.baseUrl || "",
1041
+ titleTemplate: config.titleTemplate,
1042
+ defaultTitle: config.defaultTitle,
1043
+ defaultOpenGraph: config.defaultOpenGraph,
1044
+ defaultTwitter: config.defaultTwitter,
1045
+ features: {
1046
+ openGraph: config.features?.openGraph ?? true,
1047
+ twitter: config.features?.twitter ?? true,
1048
+ robots: config.features?.robots ?? true
1049
+ }
1050
+ };
1051
+ return {
1052
+ config: fullConfig,
1053
+ render(metadata) {
1054
+ const tags = generateAllTags(metadata, fullConfig);
1055
+ const html = renderTags(tags);
1056
+ return { tags, html };
1057
+ },
1058
+ merge(parent, child) {
1059
+ return mergeMetadata(parent, child);
1060
+ },
1061
+ resolveTitle(metadata) {
1062
+ return resolveTitleWithTemplate(
1063
+ metadata.title,
1064
+ metadata.titleTemplate || fullConfig.titleTemplate,
1065
+ metadata.defaultTitle || fullConfig.defaultTitle
1066
+ );
1067
+ },
1068
+ canonical(path) {
1069
+ if (!fullConfig.baseUrl) {
1070
+ return path;
1071
+ }
1072
+ const base = fullConfig.baseUrl.endsWith("/") ? fullConfig.baseUrl.slice(0, -1) : fullConfig.baseUrl;
1073
+ const normalizedPath = path.startsWith("/") ? path : "/" + path;
1074
+ return base + normalizedPath;
1075
+ }
1076
+ };
1077
+ }
1078
+ function defineMeta(metadata) {
1079
+ return metadata;
1080
+ }
1081
+ function mergeMetadata(parent, child) {
1082
+ return {
1083
+ ...parent,
1084
+ ...child,
1085
+ // Merge Open Graph
1086
+ openGraph: child.openGraph ? { ...parent.openGraph, ...child.openGraph } : parent.openGraph,
1087
+ // Merge Twitter
1088
+ twitter: child.twitter ? { ...parent.twitter, ...child.twitter } : parent.twitter,
1089
+ // Merge Apple
1090
+ apple: child.apple ? { ...parent.apple, ...child.apple } : parent.apple,
1091
+ // Merge Icons
1092
+ icons: child.icons ? { ...parent.icons, ...child.icons } : parent.icons,
1093
+ // Merge verification
1094
+ verification: child.verification ? { ...parent.verification, ...child.verification } : parent.verification,
1095
+ // Merge other
1096
+ other: child.other ? { ...parent.other, ...child.other } : parent.other
1097
+ };
1098
+ }
1099
+ function resolveTitleWithTemplate(title, template, defaultTitle) {
1100
+ const resolvedTitle = title || defaultTitle || "";
1101
+ if (!resolvedTitle) {
1102
+ return "";
1103
+ }
1104
+ if (template && title) {
1105
+ return template.replace("%s", title);
1106
+ }
1107
+ return resolvedTitle;
1108
+ }
1109
+ function generateAllTags(metadata, config) {
1110
+ const tags = [];
1111
+ tags.push(...generateMetaTags(metadata, {
1112
+ titleTemplate: config.titleTemplate,
1113
+ defaultTitle: config.defaultTitle
1114
+ }));
1115
+ if (config.features?.openGraph !== false) {
1116
+ if (metadata.openGraph) {
1117
+ const og = { ...config.defaultOpenGraph, ...metadata.openGraph };
1118
+ tags.push(...generateOpenGraphTags(og));
1119
+ } else if (metadata.title || metadata.description) {
1120
+ tags.push(...generateOpenGraphFallback(
1121
+ metadata.title,
1122
+ metadata.description,
1123
+ metadata.canonical
1124
+ ));
1125
+ }
1126
+ }
1127
+ if (config.features?.twitter !== false) {
1128
+ if (metadata.twitter) {
1129
+ const twitter = { ...config.defaultTwitter, ...metadata.twitter };
1130
+ tags.push(...generateTwitterTags(twitter));
1131
+ } else if (metadata.title || metadata.description) {
1132
+ const ogImage = metadata.openGraph?.images?.[0];
1133
+ const imageUrl = typeof ogImage === "string" ? ogImage : ogImage?.url;
1134
+ tags.push(...generateTwitterFallback(
1135
+ metadata.title,
1136
+ metadata.description,
1137
+ imageUrl
1138
+ ));
1139
+ }
1140
+ }
1141
+ if (metadata.icons) {
1142
+ tags.push(...generateIconTags(metadata.icons));
1143
+ }
1144
+ if (metadata.apple) {
1145
+ tags.push(...generateAppleTags(metadata.apple));
1146
+ }
1147
+ return tags;
1148
+ }
1149
+ function generateIconTags(icons) {
1150
+ if (!icons) return [];
1151
+ const tags = [];
1152
+ if (icons.icon) {
1153
+ const iconList = Array.isArray(icons.icon) ? icons.icon : [icons.icon];
1154
+ iconList.forEach((icon, index) => {
1155
+ const iconData = typeof icon === "string" ? { url: icon } : icon;
1156
+ tags.push({
1157
+ tag: "link",
1158
+ attrs: {
1159
+ rel: "icon",
1160
+ href: iconData.url,
1161
+ type: iconData.type,
1162
+ sizes: iconData.sizes
1163
+ },
1164
+ key: `link:icon:${index}`
1165
+ });
1166
+ });
1167
+ }
1168
+ if (icons.shortcut) {
1169
+ const shortcut = typeof icons.shortcut === "string" ? { url: icons.shortcut } : icons.shortcut;
1170
+ tags.push({
1171
+ tag: "link",
1172
+ attrs: {
1173
+ rel: "shortcut icon",
1174
+ href: shortcut.url,
1175
+ type: shortcut.type
1176
+ },
1177
+ key: "link:shortcut-icon"
1178
+ });
1179
+ }
1180
+ if (icons.apple) {
1181
+ const appleList = Array.isArray(icons.apple) ? icons.apple : [icons.apple];
1182
+ appleList.forEach((icon, index) => {
1183
+ const iconData = typeof icon === "string" ? { url: icon } : icon;
1184
+ tags.push({
1185
+ tag: "link",
1186
+ attrs: {
1187
+ rel: "apple-touch-icon",
1188
+ href: iconData.url,
1189
+ sizes: iconData.sizes
1190
+ },
1191
+ key: `link:apple-touch-icon:${index}`
1192
+ });
1193
+ });
1194
+ }
1195
+ return tags;
1196
+ }
1197
+ function generateAppleTags(apple) {
1198
+ if (!apple) return [];
1199
+ const tags = [];
1200
+ if (apple.mobileWebAppCapable) {
1201
+ tags.push({
1202
+ tag: "meta",
1203
+ attrs: { name: "apple-mobile-web-app-capable", content: "yes" },
1204
+ key: "meta:name:apple-mobile-web-app-capable"
1205
+ });
1206
+ }
1207
+ if (apple.statusBarStyle) {
1208
+ tags.push({
1209
+ tag: "meta",
1210
+ attrs: { name: "apple-mobile-web-app-status-bar-style", content: apple.statusBarStyle },
1211
+ key: "meta:name:apple-mobile-web-app-status-bar-style"
1212
+ });
1213
+ }
1214
+ if (apple.title) {
1215
+ tags.push({
1216
+ tag: "meta",
1217
+ attrs: { name: "apple-mobile-web-app-title", content: apple.title },
1218
+ key: "meta:name:apple-mobile-web-app-title"
1219
+ });
1220
+ }
1221
+ if (apple.touchIcon) {
1222
+ const touchIcon = typeof apple.touchIcon === "string" ? { url: apple.touchIcon } : apple.touchIcon;
1223
+ tags.push({
1224
+ tag: "link",
1225
+ attrs: {
1226
+ rel: "apple-touch-icon",
1227
+ href: touchIcon.url,
1228
+ sizes: touchIcon.sizes
1229
+ },
1230
+ key: "link:apple-touch-icon-from-apple"
1231
+ });
1232
+ }
1233
+ return tags;
1234
+ }
1235
+ var VERSION = "0.1.0";
1236
+
1237
+ export { HeadManager, VERSION, clearHeadContext, createHeadManager, createSEO, defineMeta, generateAlternateTags, generateAuthorTag, generateCanonicalTag, generateCharsetTag, generateDescriptionTag, generateGooglebotTag, generateKeywordsTag, generateMetaTags, generateOpenGraphFallback, generateOpenGraphTags, generateRobotsTag, generateThemeColorTags, generateTitleTag, generateTwitterFallback, generateTwitterTags, generateVerificationTags, generateViewportTag, getHeadContext, mergeMetadata, renderTag, renderTags, setHeadContext, withHeadContext };
1238
+ //# sourceMappingURL=index.js.map
1239
+ //# sourceMappingURL=index.js.map