@rimelight/seo 0.0.5 → 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.
- package/dist/assets.d.mts +39 -0
- package/dist/assets.mjs +96 -0
- package/dist/catalog.d.mts +7 -0
- package/dist/catalog.mjs +10 -0
- package/dist/index.d.mts +9 -3
- package/dist/index.mjs +10 -2
- package/dist/integration.d.mts +3 -20
- package/dist/integration.mjs +257 -7
- package/dist/manifest.d.mts +7 -0
- package/dist/manifest.mjs +44 -0
- package/dist/og.d.mts +117 -0
- package/dist/og.mjs +302 -0
- package/dist/routes/api-catalog.d.mts +5 -0
- package/dist/routes/api-catalog.mjs +38 -0
- package/dist/routes/apple-touch-icon.png.d.mts +5 -0
- package/dist/routes/apple-touch-icon.png.mjs +17 -0
- package/dist/routes/favicon.ico.d.mts +5 -0
- package/dist/routes/favicon.ico.mjs +14 -0
- package/dist/routes/favicon.svg.d.mts +5 -0
- package/dist/routes/favicon.svg.mjs +12 -0
- package/dist/routes/icon-192.png.d.mts +5 -0
- package/dist/routes/icon-192.png.mjs +17 -0
- package/dist/routes/icon-512.png.d.mts +5 -0
- package/dist/routes/icon-512.png.mjs +17 -0
- package/dist/routes/icon-maskable-512.png.d.mts +5 -0
- package/dist/routes/icon-maskable-512.png.mjs +18 -0
- package/dist/routes/llms-full.txt.d.mts +5 -0
- package/dist/routes/llms-full.txt.mjs +57 -0
- package/dist/routes/llms.txt.d.mts +1 -0
- package/dist/routes/llms.txt.mjs +60 -12
- package/dist/routes/robots.txt.mjs +32 -4
- package/dist/routes/rss.xml.d.mts +4 -0
- package/dist/routes/rss.xml.mjs +79 -0
- package/dist/routes/security.txt.d.mts +5 -0
- package/dist/routes/security.txt.mjs +24 -0
- package/dist/routes/site.webmanifest.d.mts +5 -0
- package/dist/routes/site.webmanifest.mjs +23 -0
- package/dist/routes/sitemap.xml.mjs +18 -4
- package/dist/rss.d.mts +8 -0
- package/dist/rss.mjs +44 -0
- package/dist/security.d.mts +7 -0
- package/dist/security.mjs +34 -0
- package/dist/types.d.mts +263 -3
- package/package.json +38 -3
- package/src/assets.test.ts +70 -0
- package/src/assets.ts +137 -0
- package/src/catalog.test.ts +26 -0
- package/src/catalog.ts +11 -0
- package/src/components/SEOHead.astro +1 -1
- package/src/env.d.ts +51 -3
- package/src/index.ts +33 -2
- package/src/integration.ts +307 -28
- package/src/manifest.test.ts +41 -0
- package/src/manifest.ts +59 -0
- package/src/og.ts +436 -0
- package/src/routes/api-catalog.ts +55 -0
- package/src/routes/apple-touch-icon.png.ts +19 -0
- package/src/routes/favicon.ico.ts +19 -0
- package/src/routes/favicon.svg.ts +17 -0
- package/src/routes/icon-192.png.ts +19 -0
- package/src/routes/icon-512.png.ts +19 -0
- package/src/routes/icon-maskable-512.png.ts +24 -0
- package/src/routes/llms-full.txt.ts +77 -0
- package/src/routes/llms.txt.ts +84 -13
- package/src/routes/robots.txt.ts +47 -4
- package/src/routes/rss.xml.ts +129 -0
- package/src/routes/security.txt.ts +30 -0
- package/src/routes/site.webmanifest.ts +28 -0
- package/src/routes/sitemap.xml.ts +32 -5
- package/src/rss.test.ts +28 -0
- package/src/rss.ts +63 -0
- package/src/security.test.ts +46 -0
- package/src/security.ts +78 -0
- package/src/types.ts +286 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/assets.d.ts
|
|
2
|
+
export interface GeneratePngOptions {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
background?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GenerateMaskableOptions {
|
|
8
|
+
size?: number;
|
|
9
|
+
background?: string;
|
|
10
|
+
paddingRatio?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface GenerateOgCardOptions {
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
background?: string;
|
|
16
|
+
logoBuffer?: Buffer | Uint8Array | string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate a PNG buffer resized to specific dimensions from an SVG or image buffer.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generatePng(source: Buffer | Uint8Array | string, options: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}): Promise<Buffer>;
|
|
25
|
+
/**
|
|
26
|
+
* Generate a maskable PWA PNG icon with safe-zone padding and solid background. The standard
|
|
27
|
+
* recommendation is to place the master icon in the inner 80% safe zone.
|
|
28
|
+
*/
|
|
29
|
+
export declare function generateMaskablePng(source: Buffer | Uint8Array | string, options?: GenerateMaskableOptions): Promise<Buffer>;
|
|
30
|
+
/**
|
|
31
|
+
* Generate a valid .ico buffer containing a 32x32 PNG image according to the Windows ICO
|
|
32
|
+
* specification.
|
|
33
|
+
*/
|
|
34
|
+
export declare function generateIco(source: Buffer | Uint8Array | string, size?: number): Promise<Buffer>;
|
|
35
|
+
/**
|
|
36
|
+
* Generate a standard 1200x630 branded Open Graph card from a master logo and background color.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateOgCard(options?: GenerateOgCardOptions): Promise<Buffer>;
|
|
39
|
+
//#endregion
|
package/dist/assets.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import sharp from "sharp";
|
|
2
|
+
//#region src/assets.ts
|
|
3
|
+
/**
|
|
4
|
+
* Generate a PNG buffer resized to specific dimensions from an SVG or image buffer.
|
|
5
|
+
*/
|
|
6
|
+
async function generatePng(source, options) {
|
|
7
|
+
return sharp(source).resize(options.width, options.height, {
|
|
8
|
+
fit: "contain",
|
|
9
|
+
background: {
|
|
10
|
+
r: 0,
|
|
11
|
+
g: 0,
|
|
12
|
+
b: 0,
|
|
13
|
+
alpha: 0
|
|
14
|
+
}
|
|
15
|
+
}).png().toBuffer();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate a maskable PWA PNG icon with safe-zone padding and solid background. The standard
|
|
19
|
+
* recommendation is to place the master icon in the inner 80% safe zone.
|
|
20
|
+
*/
|
|
21
|
+
async function generateMaskablePng(source, options = {}) {
|
|
22
|
+
const { size = 512, background = "#ffffff", paddingRatio = .15 } = options;
|
|
23
|
+
const innerSize = Math.round(size * (1 - paddingRatio * 2));
|
|
24
|
+
const pad = Math.floor((size - innerSize) / 2);
|
|
25
|
+
const innerPng = await sharp(source).resize(innerSize, innerSize, {
|
|
26
|
+
fit: "contain",
|
|
27
|
+
background: {
|
|
28
|
+
r: 0,
|
|
29
|
+
g: 0,
|
|
30
|
+
b: 0,
|
|
31
|
+
alpha: 0
|
|
32
|
+
}
|
|
33
|
+
}).png().toBuffer();
|
|
34
|
+
return sharp(innerPng).extend({
|
|
35
|
+
top: pad,
|
|
36
|
+
bottom: size - innerSize - pad,
|
|
37
|
+
left: pad,
|
|
38
|
+
right: size - innerSize - pad,
|
|
39
|
+
background
|
|
40
|
+
}).png().toBuffer();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate a valid .ico buffer containing a 32x32 PNG image according to the Windows ICO
|
|
44
|
+
* specification.
|
|
45
|
+
*/
|
|
46
|
+
async function generateIco(source, size = 32) {
|
|
47
|
+
const pngBuffer = await generatePng(source, {
|
|
48
|
+
width: size,
|
|
49
|
+
height: size
|
|
50
|
+
});
|
|
51
|
+
const header = Buffer.alloc(22);
|
|
52
|
+
header.writeUInt16LE(0, 0);
|
|
53
|
+
header.writeUInt16LE(1, 2);
|
|
54
|
+
header.writeUInt16LE(1, 4);
|
|
55
|
+
header.writeUInt8(size === 256 ? 0 : size, 6);
|
|
56
|
+
header.writeUInt8(size === 256 ? 0 : size, 7);
|
|
57
|
+
header.writeUInt8(0, 8);
|
|
58
|
+
header.writeUInt8(0, 9);
|
|
59
|
+
header.writeUInt16LE(1, 10);
|
|
60
|
+
header.writeUInt16LE(32, 12);
|
|
61
|
+
header.writeUInt32LE(pngBuffer.length, 14);
|
|
62
|
+
header.writeUInt32LE(22, 18);
|
|
63
|
+
return Buffer.concat([header, pngBuffer]);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate a standard 1200x630 branded Open Graph card from a master logo and background color.
|
|
67
|
+
*/
|
|
68
|
+
async function generateOgCard(options = {}) {
|
|
69
|
+
const { width = 1200, height = 630, background = "#0a0a0a", logoBuffer } = options;
|
|
70
|
+
if (logoBuffer) {
|
|
71
|
+
const logoSize = Math.min(Math.round(height * .45), 280);
|
|
72
|
+
const logoPng = await generatePng(logoBuffer, {
|
|
73
|
+
width: logoSize,
|
|
74
|
+
height: logoSize
|
|
75
|
+
});
|
|
76
|
+
const topPad = Math.floor((height - logoSize) / 2);
|
|
77
|
+
const bottomPad = height - logoSize - topPad;
|
|
78
|
+
const leftPad = Math.floor((width - logoSize) / 2);
|
|
79
|
+
const rightPad = width - logoSize - leftPad;
|
|
80
|
+
return sharp(logoPng).extend({
|
|
81
|
+
top: topPad,
|
|
82
|
+
bottom: bottomPad,
|
|
83
|
+
left: leftPad,
|
|
84
|
+
right: rightPad,
|
|
85
|
+
background
|
|
86
|
+
}).png().toBuffer();
|
|
87
|
+
}
|
|
88
|
+
return sharp({ create: {
|
|
89
|
+
width,
|
|
90
|
+
height,
|
|
91
|
+
channels: 4,
|
|
92
|
+
background
|
|
93
|
+
} }).png().toBuffer();
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { generateIco, generateMaskablePng, generateOgCard, generatePng };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ApiCatalog, ApiCatalogOptions } from "./types.mjs";
|
|
2
|
+
//#region src/catalog.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Build RFC 9652 Application Linkset API Catalog. https://www.rfc-editor.org/rfc/rfc9652
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildApiCatalog(options: ApiCatalogOptions): ApiCatalog;
|
|
7
|
+
//#endregion
|
package/dist/catalog.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/catalog.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build RFC 9652 Application Linkset API Catalog. https://www.rfc-editor.org/rfc/rfc9652
|
|
4
|
+
*/
|
|
5
|
+
function buildApiCatalog(options) {
|
|
6
|
+
const { items } = options;
|
|
7
|
+
return { "api-catalog": items };
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { buildApiCatalog };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { generateIco, generateMaskablePng, generateOgCard, generatePng } from "./assets.mjs";
|
|
1
2
|
import { i as buildBreadcrumbSchema, n as BreadcrumbItem, r as buildArticleSchema, t as ArticleSchemaOptions } from "./schema-C8v69blQ.mjs";
|
|
2
3
|
import { a as buildLlmsTxt, i as buildLlmsFullTxt, n as LlmsPage, r as LlmsTxtOptions, t as LlmsFullTxtOptions } from "./llms-BVLxzjr-.mjs";
|
|
3
|
-
import { ChangeFreq, HeadProps, LocaleConfig, OGImage, OpenGraphMeta, RSSFeed, RobotsOptions, SeoEntry, SiteConfig, SitemapAlternate, SitemapUrl, TwitterMeta, UserAgentRule } from "./types.mjs";
|
|
4
|
+
import { ApiCatalog, ApiCatalogItem, ApiCatalogOptions, ApiCatalogRouteOptions, AssetRouteOptions, ChangeFreq, HeadProps, LlmsFullRouteOptions, LlmsRouteOptions, LocaleConfig, ManifestRouteOptions, OGImage, OpenGraphMeta, RSSFeed, RimelightSeoOptions, RobotsOptions, RobotsRouteOptions, RssCollectionConfig, RssFeedOptions, RssItem, RssOptions, SecurityRouteOptions, SecurityTxtOptions, SeoEntry, SiteConfig, SitemapAlternate, SitemapOptions, SitemapUrl, TwitterMeta, UserAgentRule, WebManifest, WebManifestIcon, WebManifestOptions, WebManifestShortcut } from "./types.mjs";
|
|
5
|
+
import { buildApiCatalog } from "./catalog.mjs";
|
|
4
6
|
import { buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, chunkSitemapUrls } from "./sitemap.mjs";
|
|
5
7
|
import { buildRobotsTxt } from "./robots.mjs";
|
|
8
|
+
import { buildRssXml, escapeXml } from "./rss.mjs";
|
|
6
9
|
import { buildCanonicalUrl, buildPageTitle, buildRobotsMetaContent, buildSafeDescription, buildWebSiteSchema, deepMerge } from "./meta.mjs";
|
|
7
|
-
import {
|
|
8
|
-
|
|
10
|
+
import { buildSecurityTxt } from "./security.mjs";
|
|
11
|
+
import { buildWebManifest } from "./manifest.mjs";
|
|
12
|
+
import { OgFontConfig, OgLayoutOptions, OgRenderConfig } from "./og.mjs";
|
|
13
|
+
import rimelightSeo from "./integration.mjs";
|
|
14
|
+
export { type ApiCatalog, type ApiCatalogItem, type ApiCatalogOptions, type ApiCatalogRouteOptions, type ArticleSchemaOptions, type AssetRouteOptions, type BreadcrumbItem, type ChangeFreq, type HeadProps, type LlmsFullRouteOptions, type LlmsFullTxtOptions, type LlmsPage, type LlmsRouteOptions, type LlmsTxtOptions, type LocaleConfig, type ManifestRouteOptions, type OGImage, type OgFontConfig, type OgLayoutOptions, type OgRenderConfig, type OpenGraphMeta, type RSSFeed, type RimelightSeoOptions, type RobotsOptions, type RobotsRouteOptions, type RssCollectionConfig, type RssFeedOptions, type RssItem, type RssOptions, type SecurityRouteOptions, type SecurityTxtOptions, type SeoEntry, type SiteConfig, type SitemapAlternate, type SitemapOptions, type SitemapUrl, type TwitterMeta, type UserAgentRule, type WebManifest, type WebManifestIcon, type WebManifestOptions, type WebManifestShortcut, buildApiCatalog, buildArticleSchema, buildBreadcrumbSchema, buildCanonicalUrl, buildLlmsFullTxt, buildLlmsTxt, buildPageTitle, buildRobotsMetaContent, buildRobotsTxt, buildRssXml, buildSafeDescription, buildSecurityTxt, buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, buildWebManifest, buildWebSiteSchema, chunkSitemapUrls, deepMerge, rimelightSeo as default, rimelightSeo, escapeXml, generateIco, generateMaskablePng, generateOgCard, generatePng };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { generateIco, generateMaskablePng, generateOgCard, generatePng } from "./assets.mjs";
|
|
2
|
+
import { buildApiCatalog } from "./catalog.mjs";
|
|
1
3
|
import { buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, chunkSitemapUrls } from "./sitemap.mjs";
|
|
2
4
|
import { buildRobotsTxt } from "./robots.mjs";
|
|
5
|
+
import { buildRssXml, escapeXml } from "./rss.mjs";
|
|
3
6
|
import { buildCanonicalUrl, buildPageTitle, buildRobotsMetaContent, buildSafeDescription, buildWebSiteSchema, deepMerge } from "./meta.mjs";
|
|
4
7
|
import { buildLlmsFullTxt, buildLlmsTxt } from "./llms.mjs";
|
|
8
|
+
import { buildSecurityTxt } from "./security.mjs";
|
|
9
|
+
import { buildWebManifest } from "./manifest.mjs";
|
|
5
10
|
import { buildArticleSchema, buildBreadcrumbSchema } from "./schema.mjs";
|
|
6
|
-
import
|
|
7
|
-
|
|
11
|
+
import rimelightSeo from "./integration.mjs";
|
|
12
|
+
//#region src/index.ts
|
|
13
|
+
var src_default = rimelightSeo;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { buildApiCatalog, buildArticleSchema, buildBreadcrumbSchema, buildCanonicalUrl, buildLlmsFullTxt, buildLlmsTxt, buildPageTitle, buildRobotsMetaContent, buildRobotsTxt, buildRssXml, buildSafeDescription, buildSecurityTxt, buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, buildWebManifest, buildWebSiteSchema, chunkSitemapUrls, deepMerge, src_default as default, escapeXml, generateIco, generateMaskablePng, generateOgCard, generatePng, rimelightSeo };
|
package/dist/integration.d.mts
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { RobotsOptions, SitemapUrl } from "./types.mjs";
|
|
1
|
+
import { RimelightSeoOptions } from "./types.mjs";
|
|
3
2
|
import { AstroIntegration } from "astro";
|
|
4
3
|
//#region src/integration.d.ts
|
|
5
|
-
export interface RimelightSeoOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Sitemap configuration or false to disable automatic /sitemap.xml
|
|
8
|
-
*/
|
|
9
|
-
sitemap?: boolean | {
|
|
10
|
-
urls?: SitemapUrl[];
|
|
11
|
-
[key: string]: any;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Robots.txt configuration or false to disable automatic /robots.txt
|
|
15
|
-
*/
|
|
16
|
-
robots?: boolean | RobotsOptions;
|
|
17
|
-
/**
|
|
18
|
-
* LLMs.txt configuration or false to disable automatic /llms.txt
|
|
19
|
-
*/
|
|
20
|
-
llms?: boolean | LlmsTxtOptions;
|
|
21
|
-
}
|
|
22
4
|
export declare function rimelightSeo(options?: RimelightSeoOptions): AstroIntegration;
|
|
23
|
-
//#endregion
|
|
5
|
+
//#endregion
|
|
6
|
+
export { type RimelightSeoOptions, rimelightSeo as default };
|
package/dist/integration.mjs
CHANGED
|
@@ -1,28 +1,278 @@
|
|
|
1
|
+
import { generateIco, generateMaskablePng, generateOgCard, generatePng } from "./assets.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
1
5
|
//#region src/integration.ts
|
|
2
6
|
function rimelightSeo(options = {}) {
|
|
3
7
|
return {
|
|
4
8
|
name: "@rimelight/seo",
|
|
5
|
-
hooks: { "astro:config:setup": ({ injectRoute, updateConfig }) => {
|
|
9
|
+
hooks: { "astro:config:setup": ({ injectRoute, updateConfig, config }) => {
|
|
10
|
+
const rootDir = fileURLToPath(config.root);
|
|
11
|
+
const findFile = (candidates) => {
|
|
12
|
+
for (const c of candidates) {
|
|
13
|
+
const resolved = path.resolve(rootDir, c);
|
|
14
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
const seoConfigPath = findFile([
|
|
19
|
+
"src/config/seo.config.ts",
|
|
20
|
+
"src/config/seo.config.js",
|
|
21
|
+
"src/config/seo.config.mjs"
|
|
22
|
+
]);
|
|
23
|
+
const siteConfigPath = findFile([
|
|
24
|
+
"src/config/site.config.ts",
|
|
25
|
+
"src/config/site.config.js",
|
|
26
|
+
"src/config/site.config.mjs"
|
|
27
|
+
]);
|
|
28
|
+
const dbPath = findFile([
|
|
29
|
+
"src/db/index.ts",
|
|
30
|
+
"src/db/index.js",
|
|
31
|
+
"src/db/index.mjs"
|
|
32
|
+
]);
|
|
33
|
+
const corpusPath = findFile([
|
|
34
|
+
"src/config/llms.corpus.ts",
|
|
35
|
+
"src/config/llms.corpus.js",
|
|
36
|
+
"src/config/llms.corpus.mjs"
|
|
37
|
+
]);
|
|
38
|
+
const assetOpts = typeof options.assets === "object" ? options.assets : {};
|
|
39
|
+
const explicitIcon = assetOpts.icon ? path.resolve(rootDir, assetOpts.icon) : null;
|
|
40
|
+
const masterIconPath = explicitIcon && fs.existsSync(explicitIcon) ? explicitIcon : findFile([
|
|
41
|
+
"src/assets/logos/logomark_color.svg",
|
|
42
|
+
"src/assets/logos/logo_color.svg",
|
|
43
|
+
"src/assets/logos/logomark.svg",
|
|
44
|
+
"src/assets/logos/logo.svg",
|
|
45
|
+
"src/assets/icon.svg",
|
|
46
|
+
"src/assets/logo.svg",
|
|
47
|
+
"src/assets/icon.png",
|
|
48
|
+
"public/favicon.svg"
|
|
49
|
+
]);
|
|
50
|
+
let masterIconBuffer = null;
|
|
51
|
+
let isSvg = false;
|
|
52
|
+
if (masterIconPath && fs.existsSync(masterIconPath)) {
|
|
53
|
+
masterIconBuffer = fs.readFileSync(masterIconPath);
|
|
54
|
+
isSvg = masterIconPath.endsWith(".svg");
|
|
55
|
+
}
|
|
56
|
+
const bgColor = assetOpts.backgroundColor || "#ffffff";
|
|
6
57
|
if (options.robots !== false) injectRoute({
|
|
7
|
-
pattern: "/robots.txt",
|
|
58
|
+
pattern: typeof options.robots === "object" && options.robots.path ? options.robots.path : "/robots.txt",
|
|
8
59
|
entrypoint: "@rimelight/seo/routes/robots.txt.ts"
|
|
9
60
|
});
|
|
10
61
|
if (options.sitemap !== false) injectRoute({
|
|
11
|
-
pattern: "/sitemap.xml",
|
|
62
|
+
pattern: typeof options.sitemap === "object" && options.sitemap.path ? options.sitemap.path : "/sitemap.xml",
|
|
12
63
|
entrypoint: "@rimelight/seo/routes/sitemap.xml.ts"
|
|
13
64
|
});
|
|
65
|
+
if (options.rss !== false) injectRoute({
|
|
66
|
+
pattern: typeof options.rss === "object" && options.rss.path ? options.rss.path : "/rss.xml",
|
|
67
|
+
entrypoint: "@rimelight/seo/routes/rss.xml.ts"
|
|
68
|
+
});
|
|
14
69
|
if (options.llms !== false) injectRoute({
|
|
15
|
-
pattern: "/llms.txt",
|
|
70
|
+
pattern: typeof options.llms === "object" && options.llms.path ? options.llms.path : "/llms.txt",
|
|
16
71
|
entrypoint: "@rimelight/seo/routes/llms.txt.ts"
|
|
17
72
|
});
|
|
73
|
+
if (options.llmsFull !== false) injectRoute({
|
|
74
|
+
pattern: typeof options.llmsFull === "object" && options.llmsFull.path ? options.llmsFull.path : "/llms-full.txt",
|
|
75
|
+
entrypoint: "@rimelight/seo/routes/llms-full.txt.ts"
|
|
76
|
+
});
|
|
77
|
+
if (options.security !== false) {
|
|
78
|
+
const pattern = typeof options.security === "object" && options.security.path ? options.security.path : "/.well-known/security.txt";
|
|
79
|
+
injectRoute({
|
|
80
|
+
pattern,
|
|
81
|
+
entrypoint: "@rimelight/seo/routes/security.txt.ts"
|
|
82
|
+
});
|
|
83
|
+
if ((typeof options.security === "object" ? options.security.legacyRoute !== false : true) && pattern === "/.well-known/security.txt") injectRoute({
|
|
84
|
+
pattern: "/security.txt",
|
|
85
|
+
entrypoint: "@rimelight/seo/routes/security.txt.ts"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (options.apiCatalog !== false) injectRoute({
|
|
89
|
+
pattern: typeof options.apiCatalog === "object" && options.apiCatalog.path ? options.apiCatalog.path : "/.well-known/api-catalog",
|
|
90
|
+
entrypoint: "@rimelight/seo/routes/api-catalog.ts"
|
|
91
|
+
});
|
|
92
|
+
if (options.manifest !== false) injectRoute({
|
|
93
|
+
pattern: typeof options.manifest === "object" && options.manifest.path ? options.manifest.path : "/site.webmanifest",
|
|
94
|
+
entrypoint: "@rimelight/seo/routes/site.webmanifest.ts"
|
|
95
|
+
});
|
|
18
96
|
updateConfig({ vite: { plugins: [{
|
|
97
|
+
name: "vite-plugin-rimelight-seo-assets",
|
|
98
|
+
configureServer(server) {
|
|
99
|
+
if (!masterIconBuffer) return;
|
|
100
|
+
server.middlewares.use(async (req, res, next) => {
|
|
101
|
+
const url = req.url?.split("?")[0];
|
|
102
|
+
if (url === "/favicon.ico" && assetOpts.faviconIco !== false) {
|
|
103
|
+
const ico = await generateIco(masterIconBuffer, 32);
|
|
104
|
+
res.setHeader("Content-Type", "image/x-icon");
|
|
105
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
106
|
+
res.end(ico);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (url === "/apple-touch-icon.png" && assetOpts.appleTouchIcon !== false) {
|
|
110
|
+
const png = await generatePng(masterIconBuffer, {
|
|
111
|
+
width: 180,
|
|
112
|
+
height: 180
|
|
113
|
+
});
|
|
114
|
+
res.setHeader("Content-Type", "image/png");
|
|
115
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
116
|
+
res.end(png);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (url === "/icon-192.png" && assetOpts.icon192 !== false) {
|
|
120
|
+
const png = await generatePng(masterIconBuffer, {
|
|
121
|
+
width: 192,
|
|
122
|
+
height: 192
|
|
123
|
+
});
|
|
124
|
+
res.setHeader("Content-Type", "image/png");
|
|
125
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
126
|
+
res.end(png);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (url === "/icon-512.png" && assetOpts.icon512 !== false) {
|
|
130
|
+
const png = await generatePng(masterIconBuffer, {
|
|
131
|
+
width: 512,
|
|
132
|
+
height: 512
|
|
133
|
+
});
|
|
134
|
+
res.setHeader("Content-Type", "image/png");
|
|
135
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
136
|
+
res.end(png);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (url === "/icon-maskable-512.png" && assetOpts.iconMaskable !== false) {
|
|
140
|
+
const png = await generateMaskablePng(masterIconBuffer, {
|
|
141
|
+
size: 512,
|
|
142
|
+
background: bgColor
|
|
143
|
+
});
|
|
144
|
+
res.setHeader("Content-Type", "image/png");
|
|
145
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
146
|
+
res.end(png);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (url === "/favicon.svg" && isSvg) {
|
|
150
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
151
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
152
|
+
res.end(masterIconBuffer);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (url === "/og.png" && assetOpts.ogImage !== false) {
|
|
156
|
+
const og = await generateOgCard({
|
|
157
|
+
logoBuffer: masterIconBuffer,
|
|
158
|
+
background: bgColor
|
|
159
|
+
});
|
|
160
|
+
res.setHeader("Content-Type", "image/png");
|
|
161
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
162
|
+
res.end(og);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
next();
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
async generateBundle() {
|
|
169
|
+
if (options.assets === false || !masterIconBuffer) return;
|
|
170
|
+
if (assetOpts.faviconIco !== false) {
|
|
171
|
+
const ico = await generateIco(masterIconBuffer, 32);
|
|
172
|
+
this.emitFile({
|
|
173
|
+
type: "asset",
|
|
174
|
+
fileName: "favicon.ico",
|
|
175
|
+
source: ico
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (assetOpts.appleTouchIcon !== false) {
|
|
179
|
+
const png = await generatePng(masterIconBuffer, {
|
|
180
|
+
width: 180,
|
|
181
|
+
height: 180
|
|
182
|
+
});
|
|
183
|
+
this.emitFile({
|
|
184
|
+
type: "asset",
|
|
185
|
+
fileName: "apple-touch-icon.png",
|
|
186
|
+
source: png
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (assetOpts.icon192 !== false) {
|
|
190
|
+
const png = await generatePng(masterIconBuffer, {
|
|
191
|
+
width: 192,
|
|
192
|
+
height: 192
|
|
193
|
+
});
|
|
194
|
+
this.emitFile({
|
|
195
|
+
type: "asset",
|
|
196
|
+
fileName: "icon-192.png",
|
|
197
|
+
source: png
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (assetOpts.icon512 !== false) {
|
|
201
|
+
const png = await generatePng(masterIconBuffer, {
|
|
202
|
+
width: 512,
|
|
203
|
+
height: 512
|
|
204
|
+
});
|
|
205
|
+
this.emitFile({
|
|
206
|
+
type: "asset",
|
|
207
|
+
fileName: "icon-512.png",
|
|
208
|
+
source: png
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (assetOpts.iconMaskable !== false) {
|
|
212
|
+
const png = await generateMaskablePng(masterIconBuffer, {
|
|
213
|
+
size: 512,
|
|
214
|
+
background: bgColor
|
|
215
|
+
});
|
|
216
|
+
this.emitFile({
|
|
217
|
+
type: "asset",
|
|
218
|
+
fileName: "icon-maskable-512.png",
|
|
219
|
+
source: png
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (assetOpts.ogImage !== false) {
|
|
223
|
+
const og = await generateOgCard({
|
|
224
|
+
logoBuffer: masterIconBuffer,
|
|
225
|
+
background: bgColor
|
|
226
|
+
});
|
|
227
|
+
this.emitFile({
|
|
228
|
+
type: "asset",
|
|
229
|
+
fileName: "og.png",
|
|
230
|
+
source: og
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (isSvg) this.emitFile({
|
|
234
|
+
type: "asset",
|
|
235
|
+
fileName: "favicon.svg",
|
|
236
|
+
source: masterIconBuffer
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}, {
|
|
19
240
|
name: "vite-plugin-rimelight-seo",
|
|
20
241
|
resolveId(id) {
|
|
21
|
-
if (id === "virtual:rimelight-seo-config") return "\0virtual:rimelight-seo
|
|
242
|
+
if (id === "virtual:rimelight-seo-config" || id === "virtual:rimelight-seo/options") return "\0virtual:rimelight-seo/options";
|
|
243
|
+
if (id === "virtual:rimelight-seo/config") return "\0virtual:rimelight-seo/config";
|
|
244
|
+
if (id === "virtual:rimelight-seo/site-config") return "\0virtual:rimelight-seo/site-config";
|
|
245
|
+
if (id === "virtual:rimelight-seo/db") return "\0virtual:rimelight-seo/db";
|
|
246
|
+
if (id === "virtual:rimelight-seo/corpus") return "\0virtual:rimelight-seo/corpus";
|
|
22
247
|
return null;
|
|
23
248
|
},
|
|
24
249
|
load(id) {
|
|
25
|
-
if (id === "\0virtual:rimelight-seo
|
|
250
|
+
if (id === "\0virtual:rimelight-seo/options") return `export default ${JSON.stringify(options)};`;
|
|
251
|
+
if (id === "\0virtual:rimelight-seo/config") {
|
|
252
|
+
if (seoConfigPath) return `import * as customConfig from ${JSON.stringify(seoConfigPath)};
|
|
253
|
+
export const SEO_LOCALES = customConfig.SEO_LOCALES;
|
|
254
|
+
export const PRIVATE_PATH_PREFIXES = customConfig.PRIVATE_PATH_PREFIXES || [];
|
|
255
|
+
export const seoEntries = customConfig.seoEntries;
|
|
256
|
+
export const rssEntries = customConfig.rssEntries;
|
|
257
|
+
export default customConfig;`;
|
|
258
|
+
return `export const SEO_LOCALES = undefined;
|
|
259
|
+
export const PRIVATE_PATH_PREFIXES = [];
|
|
260
|
+
export const seoEntries = async () => [];
|
|
261
|
+
export const rssEntries = undefined;`;
|
|
262
|
+
}
|
|
263
|
+
if (id === "\0virtual:rimelight-seo/site-config") {
|
|
264
|
+
if (siteConfigPath) return `export * from ${JSON.stringify(siteConfigPath)};`;
|
|
265
|
+
return `export const siteConfig = { name: "Site", description: "", url: "" };`;
|
|
266
|
+
}
|
|
267
|
+
if (id === "\0virtual:rimelight-seo/db") {
|
|
268
|
+
if (dbPath) return `export * from ${JSON.stringify(dbPath)};`;
|
|
269
|
+
return `export const db = null;
|
|
270
|
+
export const pages = null;`;
|
|
271
|
+
}
|
|
272
|
+
if (id === "\0virtual:rimelight-seo/corpus") {
|
|
273
|
+
if (corpusPath) return `export * from ${JSON.stringify(corpusPath)};`;
|
|
274
|
+
return `export const corpusPages = [];`;
|
|
275
|
+
}
|
|
26
276
|
return null;
|
|
27
277
|
}
|
|
28
278
|
}] } });
|
|
@@ -30,4 +280,4 @@ function rimelightSeo(options = {}) {
|
|
|
30
280
|
};
|
|
31
281
|
}
|
|
32
282
|
//#endregion
|
|
33
|
-
export { rimelightSeo };
|
|
283
|
+
export { rimelightSeo as default, rimelightSeo };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { WebManifest, WebManifestOptions } from "./types.mjs";
|
|
2
|
+
//#region src/manifest.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Build web app manifest (site.webmanifest) compliant with W3C Web App Manifest.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildWebManifest(options: WebManifestOptions): WebManifest;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/manifest.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build web app manifest (site.webmanifest) compliant with W3C Web App Manifest.
|
|
4
|
+
*/
|
|
5
|
+
function buildWebManifest(options) {
|
|
6
|
+
const { name, shortName = name, description = "", startUrl = "/", display = "standalone", backgroundColor = "#ffffff", themeColor = "#ffffff", icons = [
|
|
7
|
+
{
|
|
8
|
+
src: "/icon-192.png",
|
|
9
|
+
sizes: "192x192",
|
|
10
|
+
type: "image/png",
|
|
11
|
+
purpose: "any"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
src: "/icon-512.png",
|
|
15
|
+
sizes: "512x512",
|
|
16
|
+
type: "image/png",
|
|
17
|
+
purpose: "any"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
src: "/icon-maskable-512.png",
|
|
21
|
+
sizes: "512x512",
|
|
22
|
+
type: "image/png",
|
|
23
|
+
purpose: "maskable"
|
|
24
|
+
}
|
|
25
|
+
], shortcuts, categories, lang, dir, orientation, scope } = options;
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
short_name: shortName,
|
|
29
|
+
...description ? { description } : {},
|
|
30
|
+
start_url: startUrl,
|
|
31
|
+
display,
|
|
32
|
+
background_color: backgroundColor,
|
|
33
|
+
theme_color: themeColor,
|
|
34
|
+
icons,
|
|
35
|
+
...shortcuts ? { shortcuts } : {},
|
|
36
|
+
...categories ? { categories } : {},
|
|
37
|
+
...lang ? { lang } : {},
|
|
38
|
+
...dir ? { dir } : {},
|
|
39
|
+
...orientation ? { orientation } : {},
|
|
40
|
+
...scope ? { scope } : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { buildWebManifest };
|