@rimelight/ui 0.0.16 → 0.0.17
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 +1 -1
- package/src/integrations/sri.ts +92 -92
- package/src/middleware/index.ts +0 -1
- package/src/middleware/security.ts +81 -11
- package/src/plugins/starlightAddons/index.ts +132 -132
- package/src/plugins/starlightAddons/libs/sidebar.ts +183 -183
- package/src/plugins/starlightAddons/libs/vite.ts +46 -46
- package/src/plugins/starlightAddons/middleware.ts +132 -132
- package/src/middleware/sri.ts +0 -78
package/package.json
CHANGED
package/src/integrations/sri.ts
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import type { AstroIntegration } from "astro"
|
|
2
|
-
import { createHash } from "node:crypto"
|
|
3
|
-
|
|
4
|
-
interface SRIConfig {
|
|
5
|
-
security?: {
|
|
6
|
-
csp?: {
|
|
7
|
-
scriptDirective?: {
|
|
8
|
-
resources?: string[]
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function assertSRIConfig(obj: unknown): asserts obj is SRIConfig {
|
|
15
|
-
if (!obj || typeof obj !== "object") throw new Error("Expected config")
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Astro SRI Integration Fetches external scripts at build-time and generates hashes for the SSR
|
|
20
|
-
* middleware.
|
|
21
|
-
*/
|
|
22
|
-
export function sri(): AstroIntegration {
|
|
23
|
-
const sriManifest: Record<string, string> = {}
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
name: "sri",
|
|
27
|
-
hooks: {
|
|
28
|
-
"astro:config:setup": async ({ updateConfig, config, logger }) => {
|
|
29
|
-
logger.info("Initializing SRI Discovery...")
|
|
30
|
-
|
|
31
|
-
// 1. Identify External Scripts from CSP config
|
|
32
|
-
// We look into the custom 'security' block in astro.config.mjs
|
|
33
|
-
assertSRIConfig(config)
|
|
34
|
-
|
|
35
|
-
const security = config.security
|
|
36
|
-
const externalUrls: string[] = []
|
|
37
|
-
|
|
38
|
-
if (security?.csp?.scriptDirective?.resources) {
|
|
39
|
-
for (const resource of security.csp.scriptDirective.resources) {
|
|
40
|
-
if (resource.startsWith("https://")) {
|
|
41
|
-
externalUrls.push(resource)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2. Fetch and Hash External Scripts
|
|
47
|
-
await Promise.all(
|
|
48
|
-
externalUrls.map(async (url) => {
|
|
49
|
-
try {
|
|
50
|
-
logger.info(`Fetching ${url} for SRI hashing...`)
|
|
51
|
-
const response = await fetch(url)
|
|
52
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
53
|
-
|
|
54
|
-
const buffer = await response.arrayBuffer()
|
|
55
|
-
const hash = createHash("sha384").update(Buffer.from(buffer)).digest("base64")
|
|
56
|
-
|
|
57
|
-
sriManifest[url] = `sha384-${hash}`
|
|
58
|
-
logger.info(`Successfully hashed ${url}`)
|
|
59
|
-
} catch (err: unknown) {
|
|
60
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
61
|
-
logger.error(`Failed to fetch SRI for ${url}: ${message}`)
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// 3. Provide hashes to the runtime via Vite's virtual module
|
|
67
|
-
const virtualModuleId = "virtual:sri-manifest"
|
|
68
|
-
const resolvedVirtualModuleId = "\0" + virtualModuleId
|
|
69
|
-
|
|
70
|
-
updateConfig({
|
|
71
|
-
vite: {
|
|
72
|
-
plugins: [
|
|
73
|
-
{
|
|
74
|
-
name: "vite-plugin-sri-manifest",
|
|
75
|
-
resolveId(id: string) {
|
|
76
|
-
if (id === virtualModuleId) return resolvedVirtualModuleId
|
|
77
|
-
return null
|
|
78
|
-
},
|
|
79
|
-
load(id: string) {
|
|
80
|
-
if (id === resolvedVirtualModuleId) {
|
|
81
|
-
return `export const manifest = ${JSON.stringify(sriManifest)};`
|
|
82
|
-
}
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
1
|
+
import type { AstroIntegration } from "astro"
|
|
2
|
+
import { createHash } from "node:crypto"
|
|
3
|
+
|
|
4
|
+
interface SRIConfig {
|
|
5
|
+
security?: {
|
|
6
|
+
csp?: {
|
|
7
|
+
scriptDirective?: {
|
|
8
|
+
resources?: string[]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function assertSRIConfig(obj: unknown): asserts obj is SRIConfig {
|
|
15
|
+
if (!obj || typeof obj !== "object") throw new Error("Expected config")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Astro SRI Integration Fetches external scripts at build-time and generates hashes for the SSR
|
|
20
|
+
* middleware.
|
|
21
|
+
*/
|
|
22
|
+
export function sri(): AstroIntegration {
|
|
23
|
+
const sriManifest: Record<string, string> = {}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
name: "sri",
|
|
27
|
+
hooks: {
|
|
28
|
+
"astro:config:setup": async ({ updateConfig, config, logger }) => {
|
|
29
|
+
logger.info("Initializing SRI Discovery...")
|
|
30
|
+
|
|
31
|
+
// 1. Identify External Scripts from CSP config
|
|
32
|
+
// We look into the custom 'security' block in astro.config.mjs
|
|
33
|
+
assertSRIConfig(config)
|
|
34
|
+
|
|
35
|
+
const security = config.security
|
|
36
|
+
const externalUrls: string[] = []
|
|
37
|
+
|
|
38
|
+
if (security?.csp?.scriptDirective?.resources) {
|
|
39
|
+
for (const resource of security.csp.scriptDirective.resources) {
|
|
40
|
+
if (resource.startsWith("https://")) {
|
|
41
|
+
externalUrls.push(resource)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Fetch and Hash External Scripts
|
|
47
|
+
await Promise.all(
|
|
48
|
+
externalUrls.map(async (url) => {
|
|
49
|
+
try {
|
|
50
|
+
logger.info(`Fetching ${url} for SRI hashing...`)
|
|
51
|
+
const response = await fetch(url)
|
|
52
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
53
|
+
|
|
54
|
+
const buffer = await response.arrayBuffer()
|
|
55
|
+
const hash = createHash("sha384").update(Buffer.from(buffer)).digest("base64")
|
|
56
|
+
|
|
57
|
+
sriManifest[url] = `sha384-${hash}`
|
|
58
|
+
logger.info(`Successfully hashed ${url}`)
|
|
59
|
+
} catch (err: unknown) {
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
61
|
+
logger.error(`Failed to fetch SRI for ${url}: ${message}`)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// 3. Provide hashes to the runtime via Vite's virtual module
|
|
67
|
+
const virtualModuleId = "virtual:sri-manifest"
|
|
68
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId
|
|
69
|
+
|
|
70
|
+
updateConfig({
|
|
71
|
+
vite: {
|
|
72
|
+
plugins: [
|
|
73
|
+
{
|
|
74
|
+
name: "vite-plugin-sri-manifest",
|
|
75
|
+
resolveId(id: string) {
|
|
76
|
+
if (id === virtualModuleId) return resolvedVirtualModuleId
|
|
77
|
+
return null
|
|
78
|
+
},
|
|
79
|
+
load(id: string) {
|
|
80
|
+
if (id === resolvedVirtualModuleId) {
|
|
81
|
+
return `export const manifest = ${JSON.stringify(sriManifest)};`
|
|
82
|
+
}
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/middleware/index.ts
CHANGED
|
@@ -1,22 +1,92 @@
|
|
|
1
1
|
import { defineMiddleware } from "astro:middleware"
|
|
2
|
+
// @ts-ignore - virtual module generated by the SRI integration
|
|
3
|
+
import { manifest } from "virtual:sri-manifest"
|
|
2
4
|
|
|
5
|
+
declare const HTMLRewriter: any
|
|
6
|
+
|
|
7
|
+
function assertManifest(obj: unknown): asserts obj is Record<string, string> {
|
|
8
|
+
if (!obj || typeof obj !== "object") throw new Error("Expected manifest")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function addSecurityHeaders(response: Response): Response {
|
|
12
|
+
const newResponse = new Response(response.body, response)
|
|
13
|
+
newResponse.headers.set("Referrer-Policy", "strict-origin-when-cross-origin")
|
|
14
|
+
newResponse.headers.set("X-Content-Type-Options", "nosniff")
|
|
15
|
+
newResponse.headers.set("X-Frame-Options", "DENY")
|
|
16
|
+
newResponse.headers.set("Cross-Origin-Resource-Policy", "same-origin")
|
|
17
|
+
newResponse.headers.set(
|
|
18
|
+
"Strict-Transport-Security",
|
|
19
|
+
"max-age=31536000; includeSubDomains; preload"
|
|
20
|
+
)
|
|
21
|
+
return newResponse
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Security Middleware: Injects SRI integrity attributes and security headers.
|
|
26
|
+
*/
|
|
3
27
|
export const security = defineMiddleware(async (context, next) => {
|
|
4
|
-
// HTTP to HTTPS redirect
|
|
5
|
-
if (context.request.url.startsWith("http://")) {
|
|
28
|
+
// HTTP to HTTPS redirect only in production
|
|
29
|
+
if (import.meta.env.PROD && context.request.url.startsWith("http://")) {
|
|
6
30
|
const httpsUrl = context.request.url.replace(/^http:/, "https:")
|
|
7
31
|
return Response.redirect(httpsUrl, 301)
|
|
8
32
|
}
|
|
9
33
|
|
|
10
34
|
const response = await next()
|
|
35
|
+
const contentType = response.headers.get("content-type") || ""
|
|
11
36
|
|
|
12
|
-
|
|
37
|
+
if (!contentType.includes("text/html")) {
|
|
38
|
+
return addSecurityHeaders(response)
|
|
39
|
+
}
|
|
13
40
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
if (typeof HTMLRewriter !== "undefined") {
|
|
42
|
+
return new HTMLRewriter()
|
|
43
|
+
.on("script[src]", {
|
|
44
|
+
element(el: any) {
|
|
45
|
+
const src = el.getAttribute("src")
|
|
46
|
+
if (src && manifest[src]) {
|
|
47
|
+
el.setAttribute("integrity", manifest[src])
|
|
48
|
+
el.setAttribute("crossorigin", "anonymous")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.on('link[rel="stylesheet"][href]', {
|
|
53
|
+
element(el: any) {
|
|
54
|
+
const href = el.getAttribute("href")
|
|
55
|
+
if (href && manifest[href]) {
|
|
56
|
+
el.setAttribute("integrity", manifest[href])
|
|
57
|
+
el.setAttribute("crossorigin", "anonymous")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.transform(response)
|
|
62
|
+
}
|
|
20
63
|
|
|
21
|
-
|
|
22
|
-
|
|
64
|
+
let modifiedHtml = await response.text()
|
|
65
|
+
|
|
66
|
+
assertManifest(manifest)
|
|
67
|
+
|
|
68
|
+
const entries = Object.entries(manifest)
|
|
69
|
+
for (const [url, hash] of entries) {
|
|
70
|
+
if (!hash) continue
|
|
71
|
+
|
|
72
|
+
const scriptRegex = new RegExp(`(<script[^>]+src=["']${url}["'][^>]*)(/?>)`, "g")
|
|
73
|
+
modifiedHtml = modifiedHtml.replace(
|
|
74
|
+
scriptRegex,
|
|
75
|
+
`$1 integrity="${hash}" crossorigin="anonymous"$2`
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const linkRegex = new RegExp(`(<link[^>]+href=["']${url}["'][^>]*)(/?>)`, "g")
|
|
79
|
+
modifiedHtml = modifiedHtml.replace(
|
|
80
|
+
linkRegex,
|
|
81
|
+
`$1 integrity="${hash}" crossorigin="anonymous"$2`
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return addSecurityHeaders(
|
|
86
|
+
new Response(modifiedHtml, {
|
|
87
|
+
status: response.status,
|
|
88
|
+
statusText: response.statusText,
|
|
89
|
+
headers: new Headers(response.headers)
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
|
+
})
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
/// <reference path="./locals.d.ts" />
|
|
2
|
-
import type { StarlightPlugin, StarlightUserConfig } from "@astrojs/starlight/types"
|
|
3
|
-
import virtual from "vite-plugin-virtual"
|
|
4
|
-
import path from "node:path"
|
|
5
|
-
import { fileURLToPath } from "node:url"
|
|
6
|
-
import { z } from "astro/zod"
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
StarlightSidebarTopicsConfigSchema,
|
|
10
|
-
StarlightSidebarTopicsOptionsSchema,
|
|
11
|
-
type StarlightSidebarTopicsUserConfig,
|
|
12
|
-
type StarlightSidebarTopicsUserOptions
|
|
13
|
-
} from "./libs/config"
|
|
14
|
-
import { throwPluginError } from "./libs/plugin"
|
|
15
|
-
import { vitePluginStarlightSidebarTopics } from "./libs/vite"
|
|
16
|
-
|
|
17
|
-
export type {
|
|
18
|
-
StarlightSidebarTopicsConfig,
|
|
19
|
-
StarlightSidebarTopicsUserConfig,
|
|
20
|
-
StarlightSidebarTopicsOptions,
|
|
21
|
-
StarlightSidebarTopicsUserOptions
|
|
22
|
-
} from "./libs/config"
|
|
23
|
-
|
|
24
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
25
|
-
|
|
26
|
-
export interface StarlightAddonsConfig {
|
|
27
|
-
copy?: boolean
|
|
28
|
-
share?: boolean
|
|
29
|
-
packageManagers?: string[]
|
|
30
|
-
// Merged config for Topics
|
|
31
|
-
topics?: StarlightSidebarTopicsUserConfig
|
|
32
|
-
topicOptions?: StarlightSidebarTopicsUserOptions
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default function starlightAddons(userConfig?: StarlightAddonsConfig): StarlightPlugin {
|
|
36
|
-
// 1. Setup default and merged config for Addons
|
|
37
|
-
const defaultAddonConfig: StarlightAddonsConfig = {
|
|
38
|
-
copy: true,
|
|
39
|
-
share: true,
|
|
40
|
-
packageManagers: ["npm"]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const addonConfig = { ...defaultAddonConfig, ...userConfig }
|
|
44
|
-
|
|
45
|
-
// 2. Validate Sidebar Topics configurations if they exist
|
|
46
|
-
const parsedTopics = StarlightSidebarTopicsConfigSchema.safeParse(addonConfig.topics || [])
|
|
47
|
-
if (!parsedTopics.success) {
|
|
48
|
-
throwPluginError(
|
|
49
|
-
`Invalid topics config: ${parsedTopics.error.issues.map((i) => i.message).join("\n")}`
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const parsedOptions = StarlightSidebarTopicsOptionsSchema.safeParse(addonConfig.topicOptions)
|
|
54
|
-
if (!parsedOptions.success) {
|
|
55
|
-
throwPluginError(`Invalid topic options: ${z.prettifyError(parsedOptions.error)}`)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const topicsData = parsedTopics.data
|
|
59
|
-
const optionsData = parsedOptions.data
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
name: "rimelight-ui-starlight-addons",
|
|
63
|
-
hooks: {
|
|
64
|
-
"config:setup"({
|
|
65
|
-
addIntegration,
|
|
66
|
-
updateConfig,
|
|
67
|
-
logger,
|
|
68
|
-
addRouteMiddleware,
|
|
69
|
-
command,
|
|
70
|
-
config: starlightConfig
|
|
71
|
-
}) {
|
|
72
|
-
// --- Sidebar Topics Logic ---
|
|
73
|
-
if ((command === "dev" || command === "build") && topicsData.length > 0) {
|
|
74
|
-
if (starlightConfig.sidebar) {
|
|
75
|
-
throwPluginError(
|
|
76
|
-
"Sidebar detected. To use topics, move your sidebar items into the topics config."
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const topicSidebar: StarlightUserConfig["sidebar"] = topicsData.map((topic, index) => {
|
|
81
|
-
return "items" in topic
|
|
82
|
-
? { label: String(index), items: topic.items }
|
|
83
|
-
: { label: String(index), items: [] }
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
updateConfig({
|
|
87
|
-
sidebar: topicSidebar,
|
|
88
|
-
components: {
|
|
89
|
-
Sidebar: `starlight-sidebar-topics/overrides/Sidebar.astro`
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// --- Addons Logic ---
|
|
95
|
-
if (
|
|
96
|
-
!addonConfig.copy &&
|
|
97
|
-
!addonConfig.share &&
|
|
98
|
-
(!addonConfig.packageManagers || addonConfig.packageManagers.length === 0)
|
|
99
|
-
) {
|
|
100
|
-
logger.warn("StarlightAddons: No features enabled.")
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
updateConfig({
|
|
104
|
-
components: {
|
|
105
|
-
// This will merge with the Sidebar override if both exist
|
|
106
|
-
PageTitle: path.join(__dirname, "PageTitle.astro")
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// --- Unified Integration ---
|
|
111
|
-
addIntegration({
|
|
112
|
-
name: "rimelight-ui-starlight-addons-integration",
|
|
113
|
-
hooks: {
|
|
114
|
-
"astro:config:setup": ({ updateConfig: updateAstroConfig }) => {
|
|
115
|
-
updateAstroConfig({
|
|
116
|
-
vite: {
|
|
117
|
-
plugins: [
|
|
118
|
-
virtual({
|
|
119
|
-
"virtual:rimelight-starlight-addons-config": `export default ${JSON.stringify(addonConfig)}`
|
|
120
|
-
}),
|
|
121
|
-
// Pass the validated topic data to the vite plugin
|
|
122
|
-
vitePluginStarlightSidebarTopics(topicsData, optionsData)
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
} as Record<string, unknown>)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
1
|
+
/// <reference path="./locals.d.ts" />
|
|
2
|
+
import type { StarlightPlugin, StarlightUserConfig } from "@astrojs/starlight/types"
|
|
3
|
+
import virtual from "vite-plugin-virtual"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
import { z } from "astro/zod"
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
StarlightSidebarTopicsConfigSchema,
|
|
10
|
+
StarlightSidebarTopicsOptionsSchema,
|
|
11
|
+
type StarlightSidebarTopicsUserConfig,
|
|
12
|
+
type StarlightSidebarTopicsUserOptions
|
|
13
|
+
} from "./libs/config"
|
|
14
|
+
import { throwPluginError } from "./libs/plugin"
|
|
15
|
+
import { vitePluginStarlightSidebarTopics } from "./libs/vite"
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
StarlightSidebarTopicsConfig,
|
|
19
|
+
StarlightSidebarTopicsUserConfig,
|
|
20
|
+
StarlightSidebarTopicsOptions,
|
|
21
|
+
StarlightSidebarTopicsUserOptions
|
|
22
|
+
} from "./libs/config"
|
|
23
|
+
|
|
24
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
25
|
+
|
|
26
|
+
export interface StarlightAddonsConfig {
|
|
27
|
+
copy?: boolean
|
|
28
|
+
share?: boolean
|
|
29
|
+
packageManagers?: string[]
|
|
30
|
+
// Merged config for Topics
|
|
31
|
+
topics?: StarlightSidebarTopicsUserConfig
|
|
32
|
+
topicOptions?: StarlightSidebarTopicsUserOptions
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function starlightAddons(userConfig?: StarlightAddonsConfig): StarlightPlugin {
|
|
36
|
+
// 1. Setup default and merged config for Addons
|
|
37
|
+
const defaultAddonConfig: StarlightAddonsConfig = {
|
|
38
|
+
copy: true,
|
|
39
|
+
share: true,
|
|
40
|
+
packageManagers: ["npm"]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const addonConfig = { ...defaultAddonConfig, ...userConfig }
|
|
44
|
+
|
|
45
|
+
// 2. Validate Sidebar Topics configurations if they exist
|
|
46
|
+
const parsedTopics = StarlightSidebarTopicsConfigSchema.safeParse(addonConfig.topics || [])
|
|
47
|
+
if (!parsedTopics.success) {
|
|
48
|
+
throwPluginError(
|
|
49
|
+
`Invalid topics config: ${parsedTopics.error.issues.map((i) => i.message).join("\n")}`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const parsedOptions = StarlightSidebarTopicsOptionsSchema.safeParse(addonConfig.topicOptions)
|
|
54
|
+
if (!parsedOptions.success) {
|
|
55
|
+
throwPluginError(`Invalid topic options: ${z.prettifyError(parsedOptions.error)}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const topicsData = parsedTopics.data
|
|
59
|
+
const optionsData = parsedOptions.data
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
name: "rimelight-ui-starlight-addons",
|
|
63
|
+
hooks: {
|
|
64
|
+
"config:setup"({
|
|
65
|
+
addIntegration,
|
|
66
|
+
updateConfig,
|
|
67
|
+
logger,
|
|
68
|
+
addRouteMiddleware,
|
|
69
|
+
command,
|
|
70
|
+
config: starlightConfig
|
|
71
|
+
}) {
|
|
72
|
+
// --- Sidebar Topics Logic ---
|
|
73
|
+
if ((command === "dev" || command === "build") && topicsData.length > 0) {
|
|
74
|
+
if (starlightConfig.sidebar) {
|
|
75
|
+
throwPluginError(
|
|
76
|
+
"Sidebar detected. To use topics, move your sidebar items into the topics config."
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const topicSidebar: StarlightUserConfig["sidebar"] = topicsData.map((topic, index) => {
|
|
81
|
+
return "items" in topic
|
|
82
|
+
? { label: String(index), items: topic.items }
|
|
83
|
+
: { label: String(index), items: [] }
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
updateConfig({
|
|
87
|
+
sidebar: topicSidebar,
|
|
88
|
+
components: {
|
|
89
|
+
Sidebar: `starlight-sidebar-topics/overrides/Sidebar.astro`
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- Addons Logic ---
|
|
95
|
+
if (
|
|
96
|
+
!addonConfig.copy &&
|
|
97
|
+
!addonConfig.share &&
|
|
98
|
+
(!addonConfig.packageManagers || addonConfig.packageManagers.length === 0)
|
|
99
|
+
) {
|
|
100
|
+
logger.warn("StarlightAddons: No features enabled.")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
updateConfig({
|
|
104
|
+
components: {
|
|
105
|
+
// This will merge with the Sidebar override if both exist
|
|
106
|
+
PageTitle: path.join(__dirname, "PageTitle.astro")
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// --- Unified Integration ---
|
|
111
|
+
addIntegration({
|
|
112
|
+
name: "rimelight-ui-starlight-addons-integration",
|
|
113
|
+
hooks: {
|
|
114
|
+
"astro:config:setup": ({ updateConfig: updateAstroConfig }) => {
|
|
115
|
+
updateAstroConfig({
|
|
116
|
+
vite: {
|
|
117
|
+
plugins: [
|
|
118
|
+
virtual({
|
|
119
|
+
"virtual:rimelight-starlight-addons-config": `export default ${JSON.stringify(addonConfig)}`
|
|
120
|
+
}),
|
|
121
|
+
// Pass the validated topic data to the vite plugin
|
|
122
|
+
vitePluginStarlightSidebarTopics(topicsData, optionsData)
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
} as Record<string, unknown>)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|