@rimelight/seo 0.0.6 → 0.0.8

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 (67) 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 +194 -3
  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 +30 -7
  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 +55 -33
  43. package/src/index.ts +18 -1
  44. package/src/integration.ts +365 -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
  67. package/LICENSE +0 -21
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,55 @@
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
+ declare module "virtual:rimelight-seo-config" {
2
+ const config: import("./integration.js").RimelightSeoOptions
3
+ export default config
4
+ }
5
+
6
+ declare module "virtual:rimelight-seo/options" {
7
+ const config: import("./integration.js").RimelightSeoOptions
8
+ export default config
9
+ }
10
+
11
+ declare module "virtual:rimelight-seo/config" {
12
+ export const SEO_LOCALES: import("./types.js").LocaleConfig | undefined
13
+ export const PRIVATE_PATH_PREFIXES: string[] | undefined
14
+ export const seoEntries: (() => Promise<import("./types.js").SeoEntry[]>) | undefined
15
+ export const rssEntries: (() => Promise<import("./types.js").RssItem[]>) | undefined
16
+ }
17
+
18
+ declare module "virtual:rimelight-seo/site-config" {
19
+ export const siteConfig: import("./types.js").SiteConfig | Record<string, any>
20
+ }
21
+
22
+ declare module "virtual:rimelight-seo/db" {
23
+ export const db: any
24
+ export const pages: any
25
+ }
26
+
27
+ declare module "virtual:rimelight-seo/corpus" {
28
+ export const corpusPages: any[]
29
+ }
30
+
31
+ declare module "#db/schema" {
32
+ export const pages: any
33
+ }
34
+
35
+ declare module "drizzle-orm" {
36
+ export const and: (...args: any[]) => any
37
+ export const isNull: (column: any) => any
38
+ export const isNotNull: (column: any) => any
39
+ export const sql: any
40
+ export const eq: (column: any, value: any) => any
41
+ }
42
+
43
+ declare module "@rimelight/cms" {
44
+ export const renderCorpusMarkdown: (db: any, options: any) => Promise<string>
45
+ }
46
+
47
+ declare module "astro:content" {
48
+ export const getCollection: (collection: string) => Promise<any[]>
49
+ }
50
+
51
+ declare module "virtual:rimelight-seo/assets" {
52
+ export const masterIconBuffer: Buffer | null
53
+ export const isSvg: boolean
54
+ export const backgroundColor: string
55
+ }
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"