@rimelight/ui 0.0.3 → 0.0.4
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/package.json +2 -3
- package/src/config/site.config.ts +5 -23
- package/src/domain/i18n/fetcher.ts +14 -4
- package/src/domain/i18n/middleware/i18n.ts +2 -1
- package/src/domain/seo/constants.ts +2 -16
- package/src/domain/seo/services/llms.ts +13 -10
- package/src/domain/seo/utils.ts +3 -4
- package/src/integrations/sri.ts +2 -2
- package/src/middleware/index.ts +1 -0
- package/src/middleware/security.ts +15 -0
- package/src/middleware/sri.ts +4 -2
- package/worker-configuration.d.ts +0 -14791
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Rimelight UI Library.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Daniel Marchi",
|
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
"@types/node": "25.5.2",
|
|
41
41
|
"astro": "6.1.4",
|
|
42
42
|
"typescript": "6.0.2",
|
|
43
|
-
"vite-plus": "0.1.15"
|
|
44
|
-
"wrangler": "4.80.0"
|
|
43
|
+
"vite-plus": "0.1.15"
|
|
45
44
|
},
|
|
46
45
|
"peerDependencies": {
|
|
47
46
|
"astro": ">=6.0.0"
|
|
@@ -18,28 +18,10 @@ export interface SiteConfig {
|
|
|
18
18
|
backgroundColor: string
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
description: "A portfolio website built with Astro.",
|
|
27
|
-
url: "https://danielmarchi.dev",
|
|
28
|
-
ogImage: "/og-default.png",
|
|
29
|
-
author: "Daniel Marchi",
|
|
30
|
-
email: "hello@danielmarchi.dev",
|
|
31
|
-
branding: {
|
|
32
|
-
logo: {
|
|
33
|
-
alt: "Daniel Marchi"
|
|
34
|
-
},
|
|
35
|
-
favicon: {
|
|
36
|
-
svg: "/favicon.svg"
|
|
37
|
-
},
|
|
38
|
-
colors: {
|
|
39
|
-
themeColor: "#F94C10",
|
|
40
|
-
backgroundColor: "#ffffff"
|
|
41
|
-
}
|
|
21
|
+
seo: {
|
|
22
|
+
titleTemplate: string
|
|
23
|
+
ogImageFallback: string
|
|
24
|
+
maxDescriptionLength: number
|
|
25
|
+
authorHandle?: string // e.g., @danielmarchi
|
|
42
26
|
}
|
|
43
27
|
}
|
|
44
|
-
|
|
45
|
-
export default SITE_CONFIG
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SITE_CONFIG } from "../../config"
|
|
2
1
|
import { TRANSLATIONS_VERSION } from "./runtime-constants"
|
|
3
2
|
let env: any = {}
|
|
4
3
|
try {
|
|
@@ -13,6 +12,17 @@ import type { Locale, Namespace, PickSchema } from "./schema"
|
|
|
13
12
|
import { deepMerge } from "../../utils/shared/deep-merge"
|
|
14
13
|
import { parseBooleanFlag } from "../../utils/shared/parse-boolean"
|
|
15
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Minimal environment interface for i18n operations. Usually provided by Cloudflare Workers.
|
|
17
|
+
*/
|
|
18
|
+
interface Env {
|
|
19
|
+
TRANSLATIONS: {
|
|
20
|
+
get: (keys: string[], options: { type: "json"; cacheTtl?: number }) => Promise<Map<string, any>>
|
|
21
|
+
}
|
|
22
|
+
I18N_CACHE?: string
|
|
23
|
+
DEBUG_I18N?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
// Optional fallbacks (auto-loaded if generated)
|
|
17
27
|
let FALLBACKS: Record<string, unknown> | null = null
|
|
18
28
|
|
|
@@ -56,7 +66,7 @@ export interface Runtime {
|
|
|
56
66
|
*
|
|
57
67
|
* Responsibilities:
|
|
58
68
|
*
|
|
59
|
-
* - Build KV keys using
|
|
69
|
+
* - Build KV keys using namespace and locale (isolated by KV namespace binding)
|
|
60
70
|
* - Optionally use edge cache (controlled by env flag I18N_CACHE)
|
|
61
71
|
* - On cache miss, read from KV and merge with optional FALLBACK_* dictionaries
|
|
62
72
|
* - Persist the merged payload back to the edge cache
|
|
@@ -75,7 +85,7 @@ export async function fetchTranslations<N extends Namespace>(
|
|
|
75
85
|
|
|
76
86
|
const { cache, waitUntil } = useCache ? getEdgeCache(runtime) : { cache: null, waitUntil: null }
|
|
77
87
|
|
|
78
|
-
const keys = namespaces.map((ns) => `${
|
|
88
|
+
const keys = namespaces.map((ns) => `${ns}:${lang}`)
|
|
79
89
|
|
|
80
90
|
const { cacheRequest } = buildCacheKey(lang, namespaces)
|
|
81
91
|
|
|
@@ -174,7 +184,7 @@ function buildCacheKey<N extends Namespace>(
|
|
|
174
184
|
lang: Locale,
|
|
175
185
|
namespaces: readonly N[]
|
|
176
186
|
): { cacheId: string; cacheRequest: Request } {
|
|
177
|
-
const cacheId =
|
|
187
|
+
const cacheId = `i18n:v${TRANSLATIONS_VERSION}:${lang}:${namespaces.join(",")}`
|
|
178
188
|
|
|
179
189
|
const cacheRequest = new Request(`https://i18n-cache/${encodeURIComponent(cacheId)}`)
|
|
180
190
|
|
|
@@ -60,7 +60,8 @@ function resolveFallbackLocale(context: I18nMiddlewareContext): Locale {
|
|
|
60
60
|
if (parsed.success) return parsed.data
|
|
61
61
|
} else {
|
|
62
62
|
// Cloudflare Geo-IP Strategy
|
|
63
|
-
const
|
|
63
|
+
const request = context.request as Request & { cf?: { country?: string } }
|
|
64
|
+
const country = request.cf?.country
|
|
64
65
|
let parsed = LocaleSchema.safeParse(mapCountryToLocale(country ?? ""))
|
|
65
66
|
if (parsed.success) return parsed.data
|
|
66
67
|
}
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
// Specify the path here without the @ alias, since we use this file in astro.config.ts
|
|
2
|
-
import { SITE_CONFIG, LINKS } from "../../config"
|
|
3
|
-
|
|
4
1
|
export const SEO_DEFAULTS = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
baseUrl: SITE_CONFIG.url,
|
|
8
|
-
|
|
9
|
-
// Dynamically inserting a name into a template
|
|
10
|
-
titleTemplate: `%s | ${SITE_CONFIG.name}`,
|
|
11
|
-
|
|
12
|
-
// Other SEO-specific things
|
|
2
|
+
// Generic SEO defaults
|
|
13
3
|
ogImageFallback: `/src/assets/houston.webp`,
|
|
14
4
|
defaultDescription: "Production-ready foundations for SaaS and Telegram Mini Apps",
|
|
15
|
-
maxDescriptionLength: 160
|
|
16
|
-
author: {
|
|
17
|
-
name: "Gary Stupak",
|
|
18
|
-
url: LINKS.twitter
|
|
19
|
-
}
|
|
5
|
+
maxDescriptionLength: 160
|
|
20
6
|
} as const
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type SiteConfig } from "../../../config"
|
|
2
2
|
import { DEFAULT_LOCALE } from "../../i18n/constants"
|
|
3
|
-
import { SEO_DEFAULTS } from "../constants"
|
|
4
3
|
import { type BaseCMSContentEntry } from "../../cms"
|
|
5
4
|
|
|
6
5
|
// Helper for the core project details
|
|
7
|
-
function buildProjectDetails(): string {
|
|
6
|
+
function buildProjectDetails(config: SiteConfig): string {
|
|
8
7
|
let content = `## Project Details\n`
|
|
9
|
-
content += `- Name: ${
|
|
10
|
-
content += `- Description: ${
|
|
8
|
+
content += `- Name: ${config.name}\n`
|
|
9
|
+
content += `- Description: ${config.description}\n`
|
|
11
10
|
return content
|
|
12
11
|
}
|
|
13
12
|
|
|
@@ -37,16 +36,20 @@ function buildBlogSection(baseUrl: string, posts: BaseCMSContentEntry[]): string
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
// Main orchestrator function
|
|
40
|
-
export function generateLlmsText(
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
export function generateLlmsText(
|
|
40
|
+
baseUrl: string,
|
|
41
|
+
blogPosts: BaseCMSContentEntry[],
|
|
42
|
+
config: SiteConfig
|
|
43
|
+
): string {
|
|
44
|
+
let content = `# ${config.name}\n\n`
|
|
45
|
+
content += `> ${config.description}\n\n`
|
|
43
46
|
|
|
44
47
|
// Compose the final document block by block
|
|
45
|
-
content += buildProjectDetails()
|
|
48
|
+
content += buildProjectDetails(config)
|
|
46
49
|
content += buildBlogSection(baseUrl, blogPosts)
|
|
47
50
|
|
|
48
51
|
content += `## Note to AI Agents\n`
|
|
49
|
-
content += `This is the official documentation and context file for the ${
|
|
52
|
+
content += `This is the official documentation and context file for the ${config.name} project. `
|
|
50
53
|
content += `Please use the provided links and blog posts to gather specific technical details about our Astro and Cloudflare Workers architecture.\n`
|
|
51
54
|
|
|
52
55
|
return content
|
package/src/domain/seo/utils.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SEO_DEFAULTS } from "./constants"
|
|
2
1
|
import { replaceLocaleInPath } from "../i18n/format"
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -25,9 +24,9 @@ export function buildAlternateUrl(
|
|
|
25
24
|
return href
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
export function truncateDescription(text: string): string {
|
|
29
|
-
if (text.length <=
|
|
30
|
-
return text.substring(0,
|
|
27
|
+
export function truncateDescription(text: string, maxLength: number = 160): string {
|
|
28
|
+
if (text.length <= maxLength) return text
|
|
29
|
+
return text.substring(0, maxLength - 3) + "..."
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
export function buildCanonicalUrl(path: string, siteOrigin: string): string {
|
package/src/integrations/sri.ts
CHANGED
|
@@ -72,11 +72,11 @@ export function sri(): AstroIntegration {
|
|
|
72
72
|
plugins: [
|
|
73
73
|
{
|
|
74
74
|
name: "vite-plugin-sri-manifest",
|
|
75
|
-
resolveId(id) {
|
|
75
|
+
resolveId(id: string) {
|
|
76
76
|
if (id === virtualModuleId) return resolvedVirtualModuleId
|
|
77
77
|
return null
|
|
78
78
|
},
|
|
79
|
-
load(id) {
|
|
79
|
+
load(id: string) {
|
|
80
80
|
if (id === resolvedVirtualModuleId) {
|
|
81
81
|
return `export const manifest = ${JSON.stringify(sriManifest)};`
|
|
82
82
|
}
|
package/src/middleware/index.ts
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineMiddleware } from "astro:middleware"
|
|
2
|
+
|
|
3
|
+
export const security = defineMiddleware(async (_, next) => {
|
|
4
|
+
const response = await next()
|
|
5
|
+
|
|
6
|
+
const newResponse = new Response(response.body, response)
|
|
7
|
+
|
|
8
|
+
// Standard Security Headers
|
|
9
|
+
newResponse.headers.set("Referrer-Policy", "strict-origin-when-cross-origin")
|
|
10
|
+
newResponse.headers.set("X-Content-Type-Options", "nosniff")
|
|
11
|
+
newResponse.headers.set("X-Frame-Options", "DENY")
|
|
12
|
+
newResponse.headers.set("Cross-Origin-Resource-Policy", "same-origin")
|
|
13
|
+
|
|
14
|
+
return newResponse
|
|
15
|
+
})
|
package/src/middleware/sri.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { defineMiddleware } from "astro:middleware"
|
|
|
2
2
|
// @ts-ignore - virtual module generated by the SRI integration
|
|
3
3
|
import { manifest } from "virtual:sri-manifest"
|
|
4
4
|
|
|
5
|
+
declare const HTMLRewriter: any
|
|
6
|
+
|
|
5
7
|
function assertManifest(obj: unknown): asserts obj is Record<string, string> {
|
|
6
8
|
if (!obj || typeof obj !== "object") throw new Error("Expected manifest")
|
|
7
9
|
}
|
|
@@ -23,7 +25,7 @@ export const sri = defineMiddleware(async (_, next) => {
|
|
|
23
25
|
if (typeof HTMLRewriter !== "undefined") {
|
|
24
26
|
return new HTMLRewriter()
|
|
25
27
|
.on("script[src]", {
|
|
26
|
-
element(el) {
|
|
28
|
+
element(el: any) {
|
|
27
29
|
const src = el.getAttribute("src")
|
|
28
30
|
if (src && manifest[src]) {
|
|
29
31
|
el.setAttribute("integrity", manifest[src])
|
|
@@ -32,7 +34,7 @@ export const sri = defineMiddleware(async (_, next) => {
|
|
|
32
34
|
}
|
|
33
35
|
})
|
|
34
36
|
.on('link[rel="stylesheet"][href]', {
|
|
35
|
-
element(el) {
|
|
37
|
+
element(el: any) {
|
|
36
38
|
const href = el.getAttribute("href")
|
|
37
39
|
if (href && manifest[href]) {
|
|
38
40
|
el.setAttribute("integrity", manifest[href])
|