@rimelight/seo 0.0.6 → 0.0.7

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.
Files changed (66) hide show
  1. package/dist/assets.d.mts +39 -0
  2. package/dist/assets.mjs +96 -0
  3. package/dist/catalog.d.mts +7 -0
  4. package/dist/catalog.mjs +10 -0
  5. package/dist/index.d.mts +6 -2
  6. package/dist/index.mjs +5 -1
  7. package/dist/integration.mjs +182 -0
  8. package/dist/manifest.d.mts +7 -0
  9. package/dist/manifest.mjs +44 -0
  10. package/dist/og.mjs +1 -1
  11. package/dist/routes/api-catalog.d.mts +5 -0
  12. package/dist/routes/api-catalog.mjs +38 -0
  13. package/dist/routes/apple-touch-icon.png.d.mts +5 -0
  14. package/dist/routes/apple-touch-icon.png.mjs +17 -0
  15. package/dist/routes/favicon.ico.d.mts +5 -0
  16. package/dist/routes/favicon.ico.mjs +14 -0
  17. package/dist/routes/favicon.svg.d.mts +5 -0
  18. package/dist/routes/favicon.svg.mjs +12 -0
  19. package/dist/routes/icon-192.png.d.mts +5 -0
  20. package/dist/routes/icon-192.png.mjs +17 -0
  21. package/dist/routes/icon-512.png.d.mts +5 -0
  22. package/dist/routes/icon-512.png.mjs +17 -0
  23. package/dist/routes/icon-maskable-512.png.d.mts +5 -0
  24. package/dist/routes/icon-maskable-512.png.mjs +18 -0
  25. package/dist/routes/llms.txt.mjs +1 -1
  26. package/dist/routes/robots.txt.mjs +1 -1
  27. package/dist/routes/rss.xml.mjs +48 -20
  28. package/dist/routes/security.txt.d.mts +5 -0
  29. package/dist/routes/security.txt.mjs +24 -0
  30. package/dist/routes/site.webmanifest.d.mts +5 -0
  31. package/dist/routes/site.webmanifest.mjs +23 -0
  32. package/dist/routes/sitemap.xml.mjs +1 -1
  33. package/dist/security.d.mts +7 -0
  34. package/dist/security.mjs +34 -0
  35. package/dist/types.d.mts +191 -8
  36. package/package.json +23 -2
  37. package/src/assets.test.ts +70 -0
  38. package/src/assets.ts +137 -0
  39. package/src/catalog.test.ts +26 -0
  40. package/src/catalog.ts +11 -0
  41. package/src/components/SEOHead.astro +1 -1
  42. package/src/env.d.ts +56 -33
  43. package/src/index.ts +18 -1
  44. package/src/integration.ts +353 -172
  45. package/src/manifest.test.ts +41 -0
  46. package/src/manifest.ts +59 -0
  47. package/src/og.ts +1 -9
  48. package/src/routes/api-catalog.ts +55 -0
  49. package/src/routes/apple-touch-icon.png.ts +19 -0
  50. package/src/routes/favicon.ico.ts +19 -0
  51. package/src/routes/favicon.svg.ts +17 -0
  52. package/src/routes/icon-192.png.ts +19 -0
  53. package/src/routes/icon-512.png.ts +19 -0
  54. package/src/routes/icon-maskable-512.png.ts +24 -0
  55. package/src/routes/llms-full.txt.ts +77 -77
  56. package/src/routes/llms.txt.ts +93 -90
  57. package/src/routes/robots.txt.ts +63 -63
  58. package/src/routes/rss.xml.ts +129 -87
  59. package/src/routes/security.txt.ts +30 -0
  60. package/src/routes/site.webmanifest.ts +28 -0
  61. package/src/routes/sitemap.xml.ts +43 -43
  62. package/src/rss.test.ts +28 -28
  63. package/src/rss.ts +63 -63
  64. package/src/security.test.ts +46 -0
  65. package/src/security.ts +78 -0
  66. package/src/types.ts +564 -367
package/src/assets.ts ADDED
@@ -0,0 +1,137 @@
1
+ import sharp from "sharp"
2
+
3
+ export interface GeneratePngOptions {
4
+ width: number
5
+ height: number
6
+ background?: string
7
+ }
8
+
9
+ export interface GenerateMaskableOptions {
10
+ size?: number
11
+ background?: string
12
+ paddingRatio?: number
13
+ }
14
+
15
+ export interface GenerateOgCardOptions {
16
+ width?: number
17
+ height?: number
18
+ background?: string
19
+ logoBuffer?: Buffer | Uint8Array | string | null
20
+ }
21
+
22
+ /**
23
+ * Generate a PNG buffer resized to specific dimensions from an SVG or image buffer.
24
+ */
25
+ export async function generatePng(
26
+ source: Buffer | Uint8Array | string,
27
+ options: { width: number; height: number }
28
+ ): Promise<Buffer> {
29
+ return sharp(source)
30
+ .resize(options.width, options.height, {
31
+ fit: "contain",
32
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
33
+ })
34
+ .png()
35
+ .toBuffer()
36
+ }
37
+
38
+ /**
39
+ * Generate a maskable PWA PNG icon with safe-zone padding and solid background. The standard
40
+ * recommendation is to place the master icon in the inner 80% safe zone.
41
+ */
42
+ export async function generateMaskablePng(
43
+ source: Buffer | Uint8Array | string,
44
+ options: GenerateMaskableOptions = {}
45
+ ): Promise<Buffer> {
46
+ const { size = 512, background = "#ffffff", paddingRatio = 0.15 } = options
47
+ const innerSize = Math.round(size * (1 - paddingRatio * 2))
48
+ const pad = Math.floor((size - innerSize) / 2)
49
+
50
+ const innerPng = await sharp(source)
51
+ .resize(innerSize, innerSize, {
52
+ fit: "contain",
53
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
54
+ })
55
+ .png()
56
+ .toBuffer()
57
+
58
+ return sharp(innerPng)
59
+ .extend({
60
+ top: pad,
61
+ bottom: size - innerSize - pad,
62
+ left: pad,
63
+ right: size - innerSize - pad,
64
+ background
65
+ })
66
+ .png()
67
+ .toBuffer()
68
+ }
69
+
70
+ /**
71
+ * Generate a valid .ico buffer containing a 32x32 PNG image according to the Windows ICO
72
+ * specification.
73
+ */
74
+ export async function generateIco(
75
+ source: Buffer | Uint8Array | string,
76
+ size = 32
77
+ ): Promise<Buffer> {
78
+ const pngBuffer = await generatePng(source, { width: size, height: size })
79
+
80
+ const header = Buffer.alloc(22)
81
+ // Reserved (2 bytes)
82
+ header.writeUInt16LE(0, 0)
83
+ // Image type (1 = ICO) (2 bytes)
84
+ header.writeUInt16LE(1, 2)
85
+ // Number of images (2 bytes)
86
+ header.writeUInt16LE(1, 4)
87
+
88
+ // Directory entry (16 bytes)
89
+ header.writeUInt8(size === 256 ? 0 : size, 6) // Width
90
+ header.writeUInt8(size === 256 ? 0 : size, 7) // Height
91
+ header.writeUInt8(0, 8) // Palette size (0 = no palette)
92
+ header.writeUInt8(0, 9) // Reserved
93
+ header.writeUInt16LE(1, 10) // Color planes
94
+ header.writeUInt16LE(32, 12) // Bits per pixel (32bpp)
95
+ header.writeUInt32LE(pngBuffer.length, 14) // Image size in bytes
96
+ header.writeUInt32LE(22, 18) // Offset of image data (header size = 22)
97
+
98
+ return Buffer.concat([header, pngBuffer])
99
+ }
100
+
101
+ /**
102
+ * Generate a standard 1200x630 branded Open Graph card from a master logo and background color.
103
+ */
104
+ export async function generateOgCard(options: GenerateOgCardOptions = {}): Promise<Buffer> {
105
+ const { width = 1200, height = 630, background = "#0a0a0a", logoBuffer } = options
106
+
107
+ if (logoBuffer) {
108
+ const logoSize = Math.min(Math.round(height * 0.45), 280)
109
+ const logoPng = await generatePng(logoBuffer, { width: logoSize, height: logoSize })
110
+ const topPad = Math.floor((height - logoSize) / 2)
111
+ const bottomPad = height - logoSize - topPad
112
+ const leftPad = Math.floor((width - logoSize) / 2)
113
+ const rightPad = width - logoSize - leftPad
114
+
115
+ return sharp(logoPng)
116
+ .extend({
117
+ top: topPad,
118
+ bottom: bottomPad,
119
+ left: leftPad,
120
+ right: rightPad,
121
+ background
122
+ })
123
+ .png()
124
+ .toBuffer()
125
+ }
126
+
127
+ return sharp({
128
+ create: {
129
+ width,
130
+ height,
131
+ channels: 4,
132
+ background
133
+ }
134
+ })
135
+ .png()
136
+ .toBuffer()
137
+ }
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from "vite-plus/test"
2
+ import { buildApiCatalog } from "./catalog.js"
3
+
4
+ describe("buildApiCatalog", () => {
5
+ it("builds linkset api-catalog object", () => {
6
+ const catalog = buildApiCatalog({
7
+ items: [
8
+ {
9
+ rel: "self",
10
+ href: "/.well-known/api-catalog",
11
+ type: "application/linkset+json"
12
+ },
13
+ {
14
+ rel: "alternate",
15
+ href: "/sitemap.xml",
16
+ type: "application/xml"
17
+ }
18
+ ]
19
+ })
20
+
21
+ const items = catalog["api-catalog"]
22
+ expect(items).toHaveLength(2)
23
+ expect(items[0]?.rel).toBe("self")
24
+ expect(items[1]?.href).toBe("/sitemap.xml")
25
+ })
26
+ })
package/src/catalog.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { ApiCatalog, ApiCatalogOptions } from "./types.js"
2
+
3
+ /**
4
+ * Build RFC 9652 Application Linkset API Catalog. https://www.rfc-editor.org/rfc/rfc9652
5
+ */
6
+ export function buildApiCatalog(options: ApiCatalogOptions): ApiCatalog {
7
+ const { items } = options
8
+ return {
9
+ "api-catalog": items
10
+ }
11
+ }
@@ -39,7 +39,7 @@ const {
39
39
  // Resolve defaults from config if available
40
40
  const siteName = config?.name || 'Rimelight'
41
41
  const siteDescription = config?.description || ''
42
- const ogImageFallback = config?.seo?.ogImageFallback || config?.ogImage || ''
42
+ const ogImageFallback = config?.seo?.ogImageFallback || config?.ogImage || '/og.png'
43
43
  const themeColor = config?.branding?.colors?.themeColor
44
44
  const favicon = customFavicon || config?.branding?.favicon?.svg || '/favicon.svg'
45
45
  const faviconType = favicon.endsWith('.ico')
package/src/env.d.ts CHANGED
@@ -1,33 +1,56 @@
1
- declare module "*.astro" {
2
- export default {} as import("astro/runtime/server").AstroComponentFactory
3
- }
4
-
5
- declare module "virtual:rimelight-seo-config" {
6
- const config: import("./integration.js").RimelightSeoOptions
7
- export default config
8
- }
9
-
10
- declare module "virtual:rimelight-seo/options" {
11
- const config: import("./integration.js").RimelightSeoOptions
12
- export default config
13
- }
14
-
15
- declare module "virtual:rimelight-seo/config" {
16
- export const SEO_LOCALES: import("./types.js").LocaleConfig | undefined
17
- export const PRIVATE_PATH_PREFIXES: string[] | undefined
18
- export const seoEntries: (() => Promise<import("./types.js").SeoEntry[]>) | undefined
19
- export const rssEntries: (() => Promise<import("./types.js").RssItem[]>) | undefined
20
- }
21
-
22
- declare module "virtual:rimelight-seo/site-config" {
23
- export const siteConfig: import("./types.js").SiteConfig | Record<string, any>
24
- }
25
-
26
- declare module "virtual:rimelight-seo/db" {
27
- export const db: any
28
- export const pages: any
29
- }
30
-
31
- declare module "virtual:rimelight-seo/corpus" {
32
- export const corpusPages: any[]
33
- }
1
+
2
+ declare module "virtual:rimelight-seo-config" {
3
+ const config: import("./integration.js").RimelightSeoOptions
4
+ export default config
5
+ }
6
+
7
+ declare module "virtual:rimelight-seo/options" {
8
+ const config: import("./integration.js").RimelightSeoOptions
9
+ export default config
10
+ }
11
+
12
+ declare module "virtual:rimelight-seo/config" {
13
+ export const SEO_LOCALES: import("./types.js").LocaleConfig | undefined
14
+ export const PRIVATE_PATH_PREFIXES: string[] | undefined
15
+ export const seoEntries: (() => Promise<import("./types.js").SeoEntry[]>) | undefined
16
+ export const rssEntries: (() => Promise<import("./types.js").RssItem[]>) | undefined
17
+ }
18
+
19
+ declare module "virtual:rimelight-seo/site-config" {
20
+ export const siteConfig: import("./types.js").SiteConfig | Record<string, any>
21
+ }
22
+
23
+ declare module "virtual:rimelight-seo/db" {
24
+ export const db: any
25
+ export const pages: any
26
+ }
27
+
28
+ declare module "virtual:rimelight-seo/corpus" {
29
+ export const corpusPages: any[]
30
+ }
31
+
32
+ declare module "#db/schema" {
33
+ export const pages: any
34
+ }
35
+
36
+ declare module "drizzle-orm" {
37
+ export const and: (...args: any[]) => any
38
+ export const isNull: (column: any) => any
39
+ export const isNotNull: (column: any) => any
40
+ export const sql: any
41
+ export const eq: (column: any, value: any) => any
42
+ }
43
+
44
+ declare module "@rimelight/cms" {
45
+ export const renderCorpusMarkdown: (db: any, options: any) => Promise<string>
46
+ }
47
+
48
+ declare module "astro:content" {
49
+ export const getCollection: (collection: string) => Promise<any[]>
50
+ }
51
+
52
+ declare module "virtual:rimelight-seo/assets" {
53
+ export const masterIconBuffer: Buffer | null
54
+ export const isSvg: boolean
55
+ export const backgroundColor: string
56
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export type {
14
14
  RssItem,
15
15
  RssFeedOptions,
16
16
  RssOptions,
17
+ RssCollectionConfig,
17
18
  HeadProps,
18
19
  OpenGraphMeta,
19
20
  TwitterMeta,
@@ -23,7 +24,19 @@ export type {
23
24
  LlmsRouteOptions,
24
25
  LlmsFullRouteOptions,
25
26
  ArticleSchemaOptions,
26
- BreadcrumbItem
27
+ BreadcrumbItem,
28
+ SecurityTxtOptions,
29
+ SecurityRouteOptions,
30
+ WebManifestIcon,
31
+ WebManifestShortcut,
32
+ WebManifest,
33
+ WebManifestOptions,
34
+ ManifestRouteOptions,
35
+ ApiCatalogItem,
36
+ ApiCatalog,
37
+ ApiCatalogOptions,
38
+ ApiCatalogRouteOptions,
39
+ AssetRouteOptions
27
40
  } from "./types.js"
28
41
 
29
42
  export {
@@ -47,7 +60,11 @@ export {
47
60
  } from "./meta.js"
48
61
 
49
62
  export { buildLlmsTxt, buildLlmsFullTxt } from "./llms.js"
63
+ export { buildSecurityTxt } from "./security.js"
64
+ export { buildWebManifest } from "./manifest.js"
65
+ export { buildApiCatalog } from "./catalog.js"
50
66
  export { buildArticleSchema, buildBreadcrumbSchema } from "./schema.js"
67
+ export { generatePng, generateMaskablePng, generateIco, generateOgCard } from "./assets.js"
51
68
  export type { OgFontConfig, OgLayoutOptions, OgRenderConfig } from "./og.js"
52
69
 
53
70
  import { rimelightSeo } from "./integration.js"