@rimelight/config 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/README.md CHANGED
@@ -42,7 +42,10 @@ For Astro projects using SolidJS components:
42
42
 
43
43
  ## Vite+ Configs
44
44
 
45
+ ### App / Standard Config
46
+
45
47
  ```typescript
48
+ import { defineConfig } from "vite-plus"
46
49
  import { rimelightConfig } from "@rimelight/config/vite-plus/base"
47
50
 
48
51
  export default defineConfig({
@@ -51,6 +54,76 @@ export default defineConfig({
51
54
  })
52
55
  ```
53
56
 
57
+ ### Package Config (with Auto-Entry Discovery)
58
+
59
+ For library / workspace packages:
60
+
61
+ ```typescript
62
+ import { defineConfig } from "vite-plus"
63
+ import { rimelightPackConfig } from "@rimelight/config/vite-plus/base"
64
+
65
+ export default defineConfig(
66
+ rimelightPackConfig({
67
+ // Automatically discovers all entry modules in src/ (ignoring tests and .d.ts)
68
+ autoEntry: true
69
+ })
70
+ )
71
+ ```
72
+
73
+ ## Astro Configs
74
+
75
+ ### Full Standard Rimelight Site Config
76
+
77
+ Zero-boilerplate setup with preconfigured Cloudflare adapter, Cloudflare cache, SWR route rules, Fontsource fonts (Noto Sans, Noto Serif, JetBrains Mono), responsive image defaults, and declarative package flags:
78
+
79
+ ```typescript
80
+ import { defineConfig } from "astro/config"
81
+ import { rimelightAstroConfig } from "@rimelight/config/astro"
82
+ import en from "./src/translations/en.json"
83
+ import pt from "./src/translations/pt.json"
84
+
85
+ export default defineConfig(
86
+ rimelightAstroConfig({
87
+ // Automatically derives site: "https://example.com"
88
+ // and image.domains: ["example.com", "cdn.example.com", "cdn.rimelight.com"]
89
+ domain: "example.com",
90
+
91
+ // Declarative package flags (dynamically imported, tree-shakeable)
92
+ solid: true,
93
+ cms: true,
94
+ security: true,
95
+ i18n: {
96
+ translations: { en, pt }
97
+ },
98
+ ui: {
99
+ logos: { ... }
100
+ }
101
+ })
102
+ )
103
+ ```
104
+
105
+ ### Granular / Composable Presets
106
+
107
+ If you prefer to compose configuration piecemeal:
108
+
109
+ ```typescript
110
+ import { defineConfig } from "astro/config"
111
+ import {
112
+ cloudflarePreset,
113
+ cacheRulesPreset,
114
+ fontsPreset,
115
+ imagePreset
116
+ } from "@rimelight/config/astro"
117
+
118
+ export default defineConfig({
119
+ site: "https://example.com",
120
+ ...cloudflarePreset(),
121
+ ...cacheRulesPreset(),
122
+ ...fontsPreset(),
123
+ ...imagePreset({ domains: ["example.com"] })
124
+ })
125
+ ```
126
+
54
127
  ## Config Details
55
128
 
56
129
  ### TypeScript Base Config
package/astro/index.js ADDED
@@ -0,0 +1,329 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import cloudflare from "@astrojs/cloudflare"
4
+ import { cacheCloudflare } from "@astrojs/cloudflare/cache"
5
+ import { fontProviders } from "astro/config"
6
+
7
+ export function cacheRulesPreset(options = {}) {
8
+ const { apiSwrSeconds = 600, pageMaxAgeSeconds = 300, customRules = {} } = options
9
+ return {
10
+ routeRules: {
11
+ "/api/[...path]": {
12
+ swr: apiSwrSeconds
13
+ },
14
+ "/[...path]": {
15
+ maxAge: pageMaxAgeSeconds
16
+ },
17
+ ...customRules
18
+ }
19
+ }
20
+ }
21
+
22
+ export function fontsPreset(options = {}) {
23
+ const defaultFonts = [
24
+ {
25
+ name: "Noto Sans",
26
+ cssVariable: "--font-sans",
27
+ fallbacks: ["sans-serif"],
28
+ subsets: ["latin", "latin-ext"],
29
+ weights: [400, 500, 600, 700],
30
+ styles: ["normal"]
31
+ },
32
+ {
33
+ name: "Noto Serif",
34
+ cssVariable: "--font-serif",
35
+ fallbacks: ["serif"],
36
+ subsets: ["latin", "latin-ext"],
37
+ weights: [400, 700],
38
+ styles: ["normal"]
39
+ },
40
+ {
41
+ name: "JetBrains Mono",
42
+ cssVariable: "--font-mono",
43
+ fallbacks: ["monospace"],
44
+ subsets: ["latin", "latin-ext"],
45
+ weights: [400, 500, 700],
46
+ styles: ["normal"]
47
+ }
48
+ ]
49
+
50
+ const provider = (options.fontProviders || fontProviders)?.fontsource
51
+ ? (options.fontProviders || fontProviders).fontsource()
52
+ : undefined
53
+
54
+ return {
55
+ fonts: (options.fonts || defaultFonts).map((font) => ({
56
+ ...(provider ? { provider } : {}),
57
+ ...font
58
+ }))
59
+ }
60
+ }
61
+
62
+ export function imagePreset(options = {}) {
63
+ const { domains = [], layout = "constrained", responsiveStyles = true } = options
64
+ return {
65
+ image: {
66
+ domains,
67
+ layout,
68
+ responsiveStyles
69
+ }
70
+ }
71
+ }
72
+
73
+ export function cloudflarePreset(options = {}) {
74
+ const { adapter = cloudflare(), cacheProvider = cacheCloudflare(), session = false } = options
75
+ return {
76
+ output: "server",
77
+ session,
78
+ adapter,
79
+ cache: { provider: cacheProvider }
80
+ }
81
+ }
82
+
83
+ function discoverTranslations(dir = "./src/translations") {
84
+ const translations = {}
85
+ try {
86
+ const resolvedDir = path.resolve(process.cwd(), dir)
87
+ if (fs.existsSync(resolvedDir)) {
88
+ const files = fs.readdirSync(resolvedDir)
89
+ for (const file of files) {
90
+ if (file.endsWith(".json")) {
91
+ const locale = path.basename(file, ".json")
92
+ const content = JSON.parse(fs.readFileSync(path.join(resolvedDir, file), "utf8"))
93
+ translations[locale] = content
94
+ }
95
+ }
96
+ }
97
+ } catch {}
98
+ return translations
99
+ }
100
+
101
+ function discoverLogos(dir = "./src/assets/logos") {
102
+ try {
103
+ const candidateDirs = [dir, "./src/assets/logo"]
104
+ let resolvedDir = null
105
+ for (const d of candidateDirs) {
106
+ const p = path.resolve(process.cwd(), d)
107
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
108
+ resolvedDir = p
109
+ break
110
+ }
111
+ }
112
+ if (!resolvedDir) return undefined
113
+
114
+ const files = fs.readdirSync(resolvedDir)
115
+ const logos = {}
116
+
117
+ const variants = ["logomark", "logotype", "logo"]
118
+ const modes = ["color", "white", "black", "light", "dark"]
119
+ const extensions = [".svg", ".png", ".webp", ".jpg", ".jpeg", ".gif"]
120
+
121
+ for (const variant of variants) {
122
+ for (const mode of modes) {
123
+ for (const ext of extensions) {
124
+ const fileName = `${variant}_${mode}${ext}`
125
+ if (files.includes(fileName)) {
126
+ if (!logos[variant]) logos[variant] = {}
127
+ const relPath = `./${path.relative(process.cwd(), path.join(resolvedDir, fileName)).replace(/\\/g, "/")}`
128
+ logos[variant][mode] = relPath
129
+ }
130
+ }
131
+ }
132
+ for (const ext of extensions) {
133
+ const fileName = `${variant}${ext}`
134
+ if (files.includes(fileName) && !logos[variant]) {
135
+ const relPath = `./${path.relative(process.cwd(), path.join(resolvedDir, fileName)).replace(/\\/g, "/")}`
136
+ logos[variant] = relPath
137
+ }
138
+ }
139
+ }
140
+
141
+ return Object.keys(logos).length > 0 ? logos : undefined
142
+ } catch {
143
+ return undefined
144
+ }
145
+ }
146
+
147
+ function defaultShortcuts() {
148
+ return {
149
+ categories: {
150
+ system: { label: "System" },
151
+ navigation: { label: "Navigation" }
152
+ }
153
+ }
154
+ }
155
+
156
+ function rimelightIntegration(options, imageDomains, domain) {
157
+ return {
158
+ name: "rimelight-auto-plugins",
159
+ hooks: {
160
+ "astro:config:setup": async ({ updateConfig }) => {
161
+ const integrations = []
162
+ const plugins = []
163
+
164
+ if (options.solid) {
165
+ const solidMod = await import("@astrojs/solid-js")
166
+ const solidFn = solidMod.default || solidMod
167
+ const solidOpts =
168
+ typeof options.solid === "object"
169
+ ? options.solid
170
+ : { include: ["**/solid/**", "**/*.tsx"] }
171
+ integrations.push(solidFn(solidOpts))
172
+ }
173
+
174
+ if (options.seo) {
175
+ const seoMod = await import("@rimelight/seo")
176
+ const { rimelightSeo } = seoMod
177
+ const seoOpts = typeof options.seo === "object" ? options.seo : {}
178
+ integrations.push(rimelightSeo(seoOpts))
179
+ }
180
+
181
+ if (options.cms) {
182
+ const cmsMod = await import("@rimelight/cms/integration")
183
+ const { rimelightCms } = cmsMod
184
+ const cmsOpts = typeof options.cms === "object" ? options.cms : {}
185
+ let storage = cmsOpts.storage
186
+ if (!storage) {
187
+ const storageMod = await import("@rimelight/cms/storage")
188
+ const { r2 } = storageMod
189
+ storage = r2({ binding: cmsOpts.binding || "BLOB" })
190
+ }
191
+ integrations.push(
192
+ rimelightCms({
193
+ storage,
194
+ auth: cmsOpts.auth || "./src/auth/auth.ts",
195
+ ...cmsOpts
196
+ })
197
+ )
198
+ }
199
+
200
+ if (options.security) {
201
+ const secMod = await import("@rimelight/security")
202
+ const { security: securityFn } = secMod
203
+ const secOpts = typeof options.security === "object" ? options.security : {}
204
+ const defaultImgSrc = imageDomains.map((d) =>
205
+ d.startsWith("http://") || d.startsWith("https://") ? d : `https://${d}`
206
+ )
207
+ const imgSrc = secOpts.imgSrc
208
+ ? Array.from(new Set([...defaultImgSrc, ...secOpts.imgSrc]))
209
+ : defaultImgSrc
210
+
211
+ plugins.push(
212
+ securityFn({
213
+ domain: secOpts.domain || domain,
214
+ imgSrc,
215
+ ...secOpts
216
+ })
217
+ )
218
+ }
219
+
220
+ if (options.i18n) {
221
+ const i18nMod = await import("@rimelight/i18n")
222
+ const { i18n: i18nFn } = i18nMod
223
+ const i18nOpts = typeof options.i18n === "object" ? options.i18n : {}
224
+ const translations = i18nOpts.translations || discoverTranslations()
225
+ const locales = i18nOpts.locales || Object.keys(translations)
226
+ const defaultLocale = i18nOpts.defaultLocale || (locales.length > 0 ? locales[0] : "en")
227
+ const kvBinding =
228
+ i18nOpts.kvBinding ||
229
+ (domain ? `${domain.replaceAll(".", "-")}_translations` : undefined)
230
+
231
+ plugins.push(
232
+ i18nFn({
233
+ locales,
234
+ defaultLocale,
235
+ prefixDefaultLocale: i18nOpts.prefixDefaultLocale ?? true,
236
+ translations,
237
+ ...(kvBinding ? { kvBinding } : {}),
238
+ ...i18nOpts
239
+ })
240
+ )
241
+ }
242
+
243
+ if (options.ui) {
244
+ const uiMod = await import("@rimelight/ui")
245
+ const { ui: uiFn } = uiMod
246
+ const uiOpts = typeof options.ui === "object" ? options.ui : {}
247
+ const discoveredLogos = discoverLogos()
248
+
249
+ const mergedLogos = uiOpts.logos
250
+ ? { ...discoveredLogos, ...uiOpts.logos }
251
+ : discoveredLogos
252
+
253
+ const mergedShortcuts =
254
+ uiOpts.shortcuts === false
255
+ ? undefined
256
+ : {
257
+ ...defaultShortcuts(),
258
+ ...(typeof uiOpts.shortcuts === "object" ? uiOpts.shortcuts : {})
259
+ }
260
+
261
+ plugins.push(
262
+ uiFn({
263
+ ...(mergedLogos ? { logos: mergedLogos } : {}),
264
+ ...(mergedShortcuts ? { shortcuts: mergedShortcuts } : {}),
265
+ ...uiOpts
266
+ })
267
+ )
268
+ }
269
+
270
+ updateConfig({
271
+ integrations,
272
+ vite: {
273
+ plugins
274
+ }
275
+ })
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ export function rimelightAstroConfig(options = {}) {
282
+ const {
283
+ domain,
284
+ site = domain ? `https://${domain}` : undefined,
285
+ imageDomains = domain ? [domain, `cdn.${domain}`, "cdn.rimelight.com"] : [],
286
+ apiSwrSeconds = 600,
287
+ pageMaxAgeSeconds = 300,
288
+ adapter,
289
+ cacheProvider,
290
+ fonts,
291
+ solid,
292
+ security,
293
+ i18n,
294
+ cms,
295
+ ui,
296
+ seo,
297
+ integrations = [],
298
+ vite = {},
299
+ ...rest
300
+ } = options
301
+
302
+ const hasFlags = Boolean(solid || security || i18n || cms || ui || seo)
303
+
304
+ return {
305
+ ...(site ? { site } : {}),
306
+ prefetch: {
307
+ prefetchAll: true
308
+ },
309
+ ...cloudflarePreset({
310
+ ...(adapter !== undefined ? { adapter } : {}),
311
+ ...(cacheProvider !== undefined ? { cacheProvider } : {})
312
+ }),
313
+ ...cacheRulesPreset({
314
+ ...(apiSwrSeconds !== undefined ? { apiSwrSeconds } : {}),
315
+ ...(pageMaxAgeSeconds !== undefined ? { pageMaxAgeSeconds } : {})
316
+ }),
317
+ ...fontsPreset(fonts !== undefined ? { fonts } : {}),
318
+ ...imagePreset({ domains: imageDomains }),
319
+ markdown: {
320
+ syntaxHighlight: "prism"
321
+ },
322
+ integrations: [
323
+ ...(hasFlags ? [rimelightIntegration(options, imageDomains, domain)] : []),
324
+ ...integrations
325
+ ],
326
+ vite,
327
+ ...rest
328
+ }
329
+ }
package/astro/index.ts ADDED
@@ -0,0 +1,415 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import cloudflare from "@astrojs/cloudflare"
4
+ import { cacheCloudflare } from "@astrojs/cloudflare/cache"
5
+ import { fontProviders } from "astro/config"
6
+
7
+ export interface CacheRulesPresetOptions {
8
+ apiSwrSeconds?: number
9
+ pageMaxAgeSeconds?: number
10
+ customRules?: Record<string, any>
11
+ }
12
+
13
+ export function cacheRulesPreset(options: CacheRulesPresetOptions = {}): any {
14
+ const { apiSwrSeconds = 600, pageMaxAgeSeconds = 300, customRules = {} } = options
15
+ return {
16
+ routeRules: {
17
+ "/api/[...path]": {
18
+ swr: apiSwrSeconds
19
+ },
20
+ "/[...path]": {
21
+ maxAge: pageMaxAgeSeconds
22
+ },
23
+ ...customRules
24
+ }
25
+ }
26
+ }
27
+
28
+ export interface FontsPresetOptions {
29
+ fontProviders?: any
30
+ fonts?: any[]
31
+ }
32
+
33
+ export function fontsPreset(options: FontsPresetOptions = {}): any {
34
+ const defaultFonts = [
35
+ {
36
+ name: "Noto Sans",
37
+ cssVariable: "--font-sans",
38
+ fallbacks: ["sans-serif"],
39
+ subsets: ["latin", "latin-ext"],
40
+ weights: [400, 500, 600, 700],
41
+ styles: ["normal"]
42
+ },
43
+ {
44
+ name: "Noto Serif",
45
+ cssVariable: "--font-serif",
46
+ fallbacks: ["serif"],
47
+ subsets: ["latin", "latin-ext"],
48
+ weights: [400, 700],
49
+ styles: ["normal"]
50
+ },
51
+ {
52
+ name: "JetBrains Mono",
53
+ cssVariable: "--font-mono",
54
+ fallbacks: ["monospace"],
55
+ subsets: ["latin", "latin-ext"],
56
+ weights: [400, 500, 700],
57
+ styles: ["normal"]
58
+ }
59
+ ]
60
+
61
+ const provider = (options.fontProviders || fontProviders)?.fontsource
62
+ ? (options.fontProviders || fontProviders).fontsource()
63
+ : undefined
64
+
65
+ return {
66
+ fonts: (options.fonts || defaultFonts).map((font) => ({
67
+ ...(provider ? { provider } : {}),
68
+ ...font
69
+ }))
70
+ }
71
+ }
72
+
73
+ export interface ImagePresetOptions {
74
+ domains?: string[]
75
+ layout?: "constrained" | "fixed" | "full-width" | "responsive"
76
+ responsiveStyles?: boolean
77
+ }
78
+
79
+ export function imagePreset(options: ImagePresetOptions = {}): any {
80
+ const { domains = [], layout = "constrained", responsiveStyles = true } = options
81
+ return {
82
+ image: {
83
+ domains,
84
+ layout,
85
+ responsiveStyles
86
+ }
87
+ }
88
+ }
89
+
90
+ export interface CloudflarePresetOptions {
91
+ adapter?: any
92
+ cacheProvider?: any
93
+ session?: boolean
94
+ }
95
+
96
+ export function cloudflarePreset(options: CloudflarePresetOptions = {}): any {
97
+ const { adapter = cloudflare(), cacheProvider = cacheCloudflare(), session = false } = options
98
+ return {
99
+ output: "server" as const,
100
+ session,
101
+ adapter,
102
+ cache: { provider: cacheProvider }
103
+ }
104
+ }
105
+
106
+ export interface RimelightAstroOptions {
107
+ domain?: string
108
+ site?: string
109
+ imageDomains?: string[]
110
+ apiSwrSeconds?: number
111
+ pageMaxAgeSeconds?: number
112
+ adapter?: any
113
+ cacheProvider?: any
114
+ fonts?: any[]
115
+ solid?: boolean | { include?: string[]; [key: string]: any }
116
+ security?:
117
+ | boolean
118
+ | {
119
+ domain?: string
120
+ imgSrc?: string[]
121
+ directives?: Record<string, any>
122
+ [key: string]: any
123
+ }
124
+ i18n?:
125
+ | boolean
126
+ | {
127
+ translations?: Record<string, any>
128
+ locales?: string[]
129
+ defaultLocale?: string
130
+ prefixDefaultLocale?: boolean
131
+ kvBinding?: string
132
+ [key: string]: any
133
+ }
134
+ seo?:
135
+ | boolean
136
+ | {
137
+ robots?: boolean | Record<string, any>
138
+ sitemap?: boolean | Record<string, any>
139
+ llms?: boolean | Record<string, any>
140
+ [key: string]: any
141
+ }
142
+ cms?:
143
+ | boolean
144
+ | {
145
+ storage?: any
146
+ binding?: string
147
+ auth?: string
148
+ [key: string]: any
149
+ }
150
+ ui?:
151
+ | boolean
152
+ | {
153
+ logos?: any
154
+ shortcuts?: any
155
+ [key: string]: any
156
+ }
157
+ integrations?: any[]
158
+ vite?: {
159
+ plugins?: any[]
160
+ [key: string]: any
161
+ }
162
+ [key: string]: any
163
+ }
164
+
165
+ function discoverTranslations(dir = "./src/translations"): Record<string, any> {
166
+ const translations: Record<string, any> = {}
167
+ try {
168
+ const resolvedDir = path.resolve(process.cwd(), dir)
169
+ if (fs.existsSync(resolvedDir)) {
170
+ const files = fs.readdirSync(resolvedDir)
171
+ for (const file of files) {
172
+ if (file.endsWith(".json")) {
173
+ const locale = path.basename(file, ".json")
174
+ const content = JSON.parse(fs.readFileSync(path.join(resolvedDir, file), "utf8"))
175
+ translations[locale] = content
176
+ }
177
+ }
178
+ }
179
+ } catch {}
180
+ return translations
181
+ }
182
+
183
+ function discoverLogos(dir = "./src/assets/logos"): Record<string, any> | undefined {
184
+ try {
185
+ const candidateDirs = [dir, "./src/assets/logo"]
186
+ let resolvedDir: string | null = null
187
+ for (const d of candidateDirs) {
188
+ const p = path.resolve(process.cwd(), d)
189
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
190
+ resolvedDir = p
191
+ break
192
+ }
193
+ }
194
+ if (!resolvedDir) return undefined
195
+
196
+ const files = fs.readdirSync(resolvedDir)
197
+ const logos: Record<string, any> = {}
198
+
199
+ const variants = ["logomark", "logotype", "logo"]
200
+ const modes = ["color", "white", "black", "light", "dark"]
201
+ const extensions = [".svg", ".png", ".webp", ".jpg", ".jpeg", ".gif"]
202
+
203
+ for (const variant of variants) {
204
+ for (const mode of modes) {
205
+ for (const ext of extensions) {
206
+ const fileName = `${variant}_${mode}${ext}`
207
+ if (files.includes(fileName)) {
208
+ if (!logos[variant]) logos[variant] = {}
209
+ const relPath = `./${path.relative(process.cwd(), path.join(resolvedDir, fileName)).replace(/\\/g, "/")}`
210
+ logos[variant][mode] = relPath
211
+ }
212
+ }
213
+ }
214
+ for (const ext of extensions) {
215
+ const fileName = `${variant}${ext}`
216
+ if (files.includes(fileName) && !logos[variant]) {
217
+ const relPath = `./${path.relative(process.cwd(), path.join(resolvedDir, fileName)).replace(/\\/g, "/")}`
218
+ logos[variant] = relPath
219
+ }
220
+ }
221
+ }
222
+
223
+ return Object.keys(logos).length > 0 ? logos : undefined
224
+ } catch {
225
+ return undefined
226
+ }
227
+ }
228
+
229
+ function defaultShortcuts(): Record<string, any> {
230
+ return {
231
+ categories: {
232
+ system: { label: "System" },
233
+ navigation: { label: "Navigation" }
234
+ }
235
+ }
236
+ }
237
+
238
+ function rimelightIntegration(
239
+ options: RimelightAstroOptions,
240
+ imageDomains: string[],
241
+ domain?: string
242
+ ) {
243
+ return {
244
+ name: "rimelight-auto-plugins",
245
+ hooks: {
246
+ "astro:config:setup": async ({ updateConfig }: { updateConfig: (config: any) => void }) => {
247
+ const integrations: any[] = []
248
+ const plugins: any[] = []
249
+
250
+ if (options.solid) {
251
+ const solidMod = await import("@astrojs/solid-js")
252
+ const solidFn = (solidMod as any).default || solidMod
253
+ const solidOpts =
254
+ typeof options.solid === "object"
255
+ ? options.solid
256
+ : { include: ["**/solid/**", "**/*.tsx"] }
257
+ integrations.push(solidFn(solidOpts))
258
+ }
259
+
260
+ if (options.seo) {
261
+ const seoMod = await import("@rimelight/seo")
262
+ const { rimelightSeo } = seoMod as any
263
+ const seoOpts = typeof options.seo === "object" ? options.seo : {}
264
+ integrations.push(rimelightSeo(seoOpts))
265
+ }
266
+
267
+ if (options.cms) {
268
+ const cmsMod = await import("@rimelight/cms/integration")
269
+ const { rimelightCms } = cmsMod as any
270
+ const cmsOpts = typeof options.cms === "object" ? options.cms : {}
271
+ let storage = cmsOpts.storage
272
+ if (!storage) {
273
+ const storageMod = await import("@rimelight/cms/storage")
274
+ const { r2 } = storageMod as any
275
+ storage = r2({ binding: cmsOpts.binding || "BLOB" })
276
+ }
277
+ integrations.push(
278
+ rimelightCms({
279
+ storage,
280
+ auth: cmsOpts.auth || "./src/auth/auth.ts",
281
+ ...cmsOpts
282
+ })
283
+ )
284
+ }
285
+
286
+ if (options.security) {
287
+ const secMod = await import("@rimelight/security")
288
+ const { security: securityFn } = secMod as any
289
+ const secOpts = typeof options.security === "object" ? options.security : {}
290
+ const defaultImgSrc = imageDomains.map((d: string) =>
291
+ d.startsWith("http://") || d.startsWith("https://") ? d : `https://${d}`
292
+ )
293
+ const imgSrc = secOpts.imgSrc
294
+ ? Array.from(new Set([...defaultImgSrc, ...secOpts.imgSrc]))
295
+ : defaultImgSrc
296
+
297
+ plugins.push(
298
+ securityFn({
299
+ domain: secOpts.domain || domain,
300
+ imgSrc,
301
+ ...secOpts
302
+ })
303
+ )
304
+ }
305
+
306
+ if (options.i18n) {
307
+ const i18nMod = await import("@rimelight/i18n")
308
+ const { i18n: i18nFn } = i18nMod as any
309
+ const i18nOpts = typeof options.i18n === "object" ? options.i18n : {}
310
+ const translations = i18nOpts.translations || discoverTranslations()
311
+ const locales = i18nOpts.locales || Object.keys(translations)
312
+ const defaultLocale = i18nOpts.defaultLocale || (locales.length > 0 ? locales[0] : "en")
313
+ const kvBinding =
314
+ i18nOpts.kvBinding ||
315
+ (domain ? `${domain.replaceAll(".", "-")}_translations` : undefined)
316
+
317
+ plugins.push(
318
+ i18nFn({
319
+ locales,
320
+ defaultLocale,
321
+ prefixDefaultLocale: i18nOpts.prefixDefaultLocale ?? true,
322
+ translations,
323
+ ...(kvBinding ? { kvBinding } : {}),
324
+ ...i18nOpts
325
+ })
326
+ )
327
+ }
328
+
329
+ if (options.ui) {
330
+ const uiMod = await import("@rimelight/ui")
331
+ const { ui: uiFn } = uiMod as any
332
+ const uiOpts = typeof options.ui === "object" ? options.ui : {}
333
+ const discoveredLogos = discoverLogos()
334
+
335
+ const mergedLogos = uiOpts.logos
336
+ ? { ...discoveredLogos, ...uiOpts.logos }
337
+ : discoveredLogos
338
+
339
+ const mergedShortcuts =
340
+ uiOpts.shortcuts === false
341
+ ? undefined
342
+ : {
343
+ ...defaultShortcuts(),
344
+ ...(typeof uiOpts.shortcuts === "object" ? uiOpts.shortcuts : {})
345
+ }
346
+
347
+ plugins.push(
348
+ uiFn({
349
+ ...(mergedLogos ? { logos: mergedLogos } : {}),
350
+ ...(mergedShortcuts ? { shortcuts: mergedShortcuts } : {}),
351
+ ...uiOpts
352
+ })
353
+ )
354
+ }
355
+
356
+ updateConfig({
357
+ integrations,
358
+ vite: {
359
+ plugins
360
+ }
361
+ })
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ export function rimelightAstroConfig(options: RimelightAstroOptions = {}): any {
368
+ const {
369
+ domain,
370
+ site = domain ? `https://${domain}` : undefined,
371
+ imageDomains = domain ? [domain, `cdn.${domain}`, "cdn.rimelight.com"] : [],
372
+ apiSwrSeconds = 600,
373
+ pageMaxAgeSeconds = 300,
374
+ adapter,
375
+ cacheProvider,
376
+ fonts,
377
+ solid,
378
+ security,
379
+ i18n,
380
+ cms,
381
+ ui,
382
+ seo,
383
+ integrations = [],
384
+ vite = {},
385
+ ...rest
386
+ } = options
387
+
388
+ const hasFlags = Boolean(solid || security || i18n || cms || ui || seo)
389
+
390
+ return {
391
+ ...(site ? { site } : {}),
392
+ prefetch: {
393
+ prefetchAll: true
394
+ },
395
+ ...cloudflarePreset({
396
+ ...(adapter !== undefined ? { adapter } : {}),
397
+ ...(cacheProvider !== undefined ? { cacheProvider } : {})
398
+ }),
399
+ ...cacheRulesPreset({
400
+ ...(apiSwrSeconds !== undefined ? { apiSwrSeconds } : {}),
401
+ ...(pageMaxAgeSeconds !== undefined ? { pageMaxAgeSeconds } : {})
402
+ }),
403
+ ...fontsPreset(fonts !== undefined ? { fonts } : {}),
404
+ ...imagePreset({ domains: imageDomains }),
405
+ markdown: {
406
+ syntaxHighlight: "prism" as const
407
+ },
408
+ integrations: [
409
+ ...(hasFlags ? [rimelightIntegration(options, imageDomains, domain)] : []),
410
+ ...integrations
411
+ ],
412
+ vite,
413
+ ...rest
414
+ }
415
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/config",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment's shared configuration package",
6
6
  "homepage": "https://rimelight.com/docs",
@@ -18,6 +18,7 @@
18
18
  "files": [
19
19
  "tsconfig",
20
20
  "vite-plus",
21
+ "astro",
21
22
  "src"
22
23
  ],
23
24
  "type": "module",
@@ -28,16 +29,58 @@
28
29
  "./vite-plus/base": {
29
30
  "types": "./vite-plus/base.ts",
30
31
  "default": "./vite-plus/base.js"
32
+ },
33
+ "./astro": {
34
+ "types": "./astro/index.ts",
35
+ "default": "./astro/index.js"
31
36
  }
32
37
  },
33
38
  "publishConfig": {
34
39
  "access": "public"
35
40
  },
36
41
  "devDependencies": {
42
+ "@astrojs/cloudflare": "14.3.1",
43
+ "astro": "7.3.2",
37
44
  "typescript": "6.0.3",
38
- "vite-plus": "0.3.0"
45
+ "vite-plus": "0.3.1"
46
+ },
47
+ "peerDependencies": {
48
+ "@astrojs/cloudflare": ">=14.0.0",
49
+ "@astrojs/solid-js": ">=7.0.0",
50
+ "@rimelight/cms": ">=0.0.1",
51
+ "@rimelight/i18n": ">=0.0.1",
52
+ "@rimelight/security": ">=0.0.1",
53
+ "@rimelight/seo": ">=0.0.1",
54
+ "@rimelight/ui": ">=0.0.1",
55
+ "astro": ">=7.0.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@astrojs/cloudflare": {
59
+ "optional": true
60
+ },
61
+ "@astrojs/solid-js": {
62
+ "optional": true
63
+ },
64
+ "@rimelight/cms": {
65
+ "optional": true
66
+ },
67
+ "@rimelight/i18n": {
68
+ "optional": true
69
+ },
70
+ "@rimelight/security": {
71
+ "optional": true
72
+ },
73
+ "@rimelight/seo": {
74
+ "optional": true
75
+ },
76
+ "@rimelight/ui": {
77
+ "optional": true
78
+ },
79
+ "astro": {
80
+ "optional": true
81
+ }
39
82
  },
40
83
  "engines": {
41
- "node": ">=26.7.0"
84
+ "node": ">=26.8.2"
42
85
  }
43
86
  }
package/vite-plus/base.js CHANGED
@@ -1,3 +1,42 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+
4
+ export function findSourceEntries(baseDir = "src") {
5
+ if (!fs.existsSync(baseDir)) {
6
+ return []
7
+ }
8
+
9
+ const entries = []
10
+
11
+ function walk(currentDir) {
12
+ const files = fs.readdirSync(currentDir, { withFileTypes: true })
13
+ for (const file of files) {
14
+ const fullPath = path.join(currentDir, file.name)
15
+ if (file.isDirectory()) {
16
+ walk(fullPath)
17
+ } else if (file.isFile()) {
18
+ const normalized = fullPath.split(path.sep).join("/")
19
+ if (
20
+ (file.name.endsWith(".ts") ||
21
+ file.name.endsWith(".tsx") ||
22
+ file.name.endsWith(".js") ||
23
+ file.name.endsWith(".jsx")) &&
24
+ !file.name.endsWith(".d.ts") &&
25
+ !file.name.endsWith(".test.ts") &&
26
+ !file.name.endsWith(".test.tsx") &&
27
+ !file.name.endsWith(".spec.ts") &&
28
+ !file.name.endsWith(".spec.tsx")
29
+ ) {
30
+ entries.push(normalized)
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ walk(baseDir)
37
+ return entries.sort((a, b) => a.localeCompare(b))
38
+ }
39
+
1
40
  export function rimelightConfig() {
2
41
  return {
3
42
  lint: {
@@ -17,18 +56,99 @@ export function rimelightConfig() {
17
56
  },
18
57
  staged: {
19
58
  "*": "vp check --fix"
59
+ },
60
+ test: {
61
+ environment: "node"
62
+ }
63
+ }
64
+ }
65
+
66
+ export function findPackageExternals(pkgJsonPath = "package.json") {
67
+ try {
68
+ const fullPath = path.resolve(process.cwd(), pkgJsonPath)
69
+ if (!fs.existsSync(fullPath)) return ["@rimelight/*"]
70
+ const pkg = JSON.parse(fs.readFileSync(fullPath, "utf8"))
71
+ const externals = new Set()
72
+
73
+ if (pkg.peerDependencies) {
74
+ Object.keys(pkg.peerDependencies).forEach((dep) => externals.add(dep))
20
75
  }
76
+ if (pkg.dependencies) {
77
+ Object.keys(pkg.dependencies).forEach((dep) => externals.add(dep))
78
+ }
79
+
80
+ return Array.from(externals)
81
+ } catch {
82
+ return []
83
+ }
84
+ }
85
+
86
+ export function verifyPackageExports(pkgJsonPath = "package.json", srcDir = "src") {
87
+ try {
88
+ const fullPath = path.resolve(process.cwd(), pkgJsonPath)
89
+ if (!fs.existsSync(fullPath)) {
90
+ return { valid: false, missingExports: ["package.json not found"], extraExports: [] }
91
+ }
92
+ const pkg = JSON.parse(fs.readFileSync(fullPath, "utf8"))
93
+ const exportsMap = pkg.exports || {}
94
+ const sourceEntries = findSourceEntries(srcDir)
95
+
96
+ const expectedKeys = sourceEntries.map((file) => {
97
+ const rel = file.replace(new RegExp(`^${srcDir}/`), "").replace(/\.(ts|tsx|js|jsx)$/, "")
98
+ return rel === "index" ? "." : `./${rel}`
99
+ })
100
+
101
+ const definedKeys = Object.keys(exportsMap)
102
+ const missingExports = expectedKeys.filter(
103
+ (k) => !definedKeys.includes(k) && !definedKeys.includes("./*")
104
+ )
105
+ const extraExports = definedKeys.filter(
106
+ (k) => k !== "." && k !== "./*" && !k.includes("*") && !expectedKeys.includes(k)
107
+ )
108
+
109
+ return {
110
+ valid: missingExports.length === 0,
111
+ missingExports,
112
+ extraExports
113
+ }
114
+ } catch (err) {
115
+ return { valid: false, missingExports: [err.message], extraExports: [] }
21
116
  }
22
117
  }
23
118
 
24
119
  export function rimelightPackConfig(options = {}) {
25
- const { entry = ["src/index.ts"], pack = {}, ...rest } = options
120
+ const { entry, autoEntry, pack = {}, autoExternal = true, ...rest } = options
121
+
122
+ let resolvedEntry = ["src/index.ts"]
123
+
124
+ if (Array.isArray(entry)) {
125
+ resolvedEntry = entry
126
+ } else if (entry === "auto" || autoEntry) {
127
+ const dir = typeof autoEntry === "string" ? autoEntry : "src"
128
+ const discovered = findSourceEntries(dir)
129
+ if (discovered.length > 0) {
130
+ resolvedEntry = discovered
131
+ }
132
+ } else if (typeof entry === "string") {
133
+ resolvedEntry = [entry]
134
+ }
135
+
136
+ const discoveredExternals = autoExternal ? findPackageExternals() : []
137
+ const userExternal = pack["external"] || []
138
+ const userExternalList = Array.isArray(userExternal) ? userExternal : [userExternal]
139
+ const userNeverBundle = pack["deps"]?.["neverBundle"] || []
140
+ const userNeverBundleList = Array.isArray(userNeverBundle) ? userNeverBundle : [userNeverBundle]
141
+ const mergedExternal = Array.from(
142
+ new Set([...userExternalList, ...userNeverBundleList, ...discoveredExternals])
143
+ ).filter(Boolean)
144
+
26
145
  return {
27
146
  ...rimelightConfig(),
28
147
  ...rest,
29
148
  pack: {
30
- entry,
149
+ entry: resolvedEntry,
31
150
  dts: true,
151
+ ...(mergedExternal.length > 0 ? { deps: { neverBundle: mergedExternal } } : {}),
32
152
  ...pack
33
153
  }
34
154
  }
package/vite-plus/base.ts CHANGED
@@ -1,5 +1,43 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
1
3
  import type { UserConfig } from "vite-plus"
2
4
 
5
+ export function findSourceEntries(baseDir = "src"): string[] {
6
+ if (!fs.existsSync(baseDir)) {
7
+ return []
8
+ }
9
+
10
+ const entries: string[] = []
11
+
12
+ function walk(currentDir: string) {
13
+ const files = fs.readdirSync(currentDir, { withFileTypes: true })
14
+ for (const file of files) {
15
+ const fullPath = path.join(currentDir, file.name)
16
+ if (file.isDirectory()) {
17
+ walk(fullPath)
18
+ } else if (file.isFile()) {
19
+ const normalized = fullPath.split(path.sep).join("/")
20
+ if (
21
+ (file.name.endsWith(".ts") ||
22
+ file.name.endsWith(".tsx") ||
23
+ file.name.endsWith(".js") ||
24
+ file.name.endsWith(".jsx")) &&
25
+ !file.name.endsWith(".d.ts") &&
26
+ !file.name.endsWith(".test.ts") &&
27
+ !file.name.endsWith(".test.tsx") &&
28
+ !file.name.endsWith(".spec.ts") &&
29
+ !file.name.endsWith(".spec.tsx")
30
+ ) {
31
+ entries.push(normalized)
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ walk(baseDir)
38
+ return entries.sort((a, b) => a.localeCompare(b))
39
+ }
40
+
3
41
  export function rimelightConfig(): UserConfig {
4
42
  return {
5
43
  lint: {
@@ -19,20 +57,116 @@ export function rimelightConfig(): UserConfig {
19
57
  },
20
58
  staged: {
21
59
  "*": "vp check --fix"
60
+ },
61
+ test: {
62
+ environment: "node"
22
63
  }
23
64
  }
24
65
  }
25
66
 
26
- export function rimelightPackConfig(
27
- options: { entry?: string[]; pack?: Record<string, any>; [key: string]: any } = {}
28
- ): any {
29
- const { entry = ["src/index.ts"], pack = {}, ...rest } = options
67
+ export function findPackageExternals(pkgJsonPath = "package.json"): string[] {
68
+ try {
69
+ const fullPath = path.resolve(process.cwd(), pkgJsonPath)
70
+ if (!fs.existsSync(fullPath)) return ["@rimelight/*"]
71
+ const pkg = JSON.parse(fs.readFileSync(fullPath, "utf8"))
72
+ const externals = new Set<string>()
73
+
74
+ if (pkg.peerDependencies) {
75
+ Object.keys(pkg.peerDependencies).forEach((dep) => externals.add(dep))
76
+ }
77
+ if (pkg.dependencies) {
78
+ Object.keys(pkg.dependencies).forEach((dep) => externals.add(dep))
79
+ }
80
+
81
+ return Array.from(externals)
82
+ } catch {
83
+ return []
84
+ }
85
+ }
86
+
87
+ export interface ExportValidationResult {
88
+ valid: boolean
89
+ missingExports: string[]
90
+ extraExports: string[]
91
+ }
92
+
93
+ export function verifyPackageExports(
94
+ pkgJsonPath = "package.json",
95
+ srcDir = "src"
96
+ ): ExportValidationResult {
97
+ try {
98
+ const fullPath = path.resolve(process.cwd(), pkgJsonPath)
99
+ if (!fs.existsSync(fullPath)) {
100
+ return { valid: false, missingExports: ["package.json not found"], extraExports: [] }
101
+ }
102
+ const pkg = JSON.parse(fs.readFileSync(fullPath, "utf8"))
103
+ const exportsMap = pkg.exports || {}
104
+ const sourceEntries = findSourceEntries(srcDir)
105
+
106
+ const expectedKeys = sourceEntries.map((file) => {
107
+ const rel = file.replace(new RegExp(`^${srcDir}/`), "").replace(/\.(ts|tsx|js|jsx)$/, "")
108
+ return rel === "index" ? "." : `./${rel}`
109
+ })
110
+
111
+ const definedKeys = Object.keys(exportsMap)
112
+ const missingExports = expectedKeys.filter(
113
+ (k) => !definedKeys.includes(k) && !definedKeys.includes("./*")
114
+ )
115
+ const extraExports = definedKeys.filter(
116
+ (k) => k !== "." && k !== "./*" && !k.includes("*") && !expectedKeys.includes(k)
117
+ )
118
+
119
+ return {
120
+ valid: missingExports.length === 0,
121
+ missingExports,
122
+ extraExports
123
+ }
124
+ } catch (err: any) {
125
+ return { valid: false, missingExports: [err.message], extraExports: [] }
126
+ }
127
+ }
128
+
129
+ export interface RimelightPackOptions {
130
+ entry?: string[] | "auto"
131
+ autoEntry?: boolean | string
132
+ pack?: Record<string, any>
133
+ autoExternal?: boolean
134
+ [key: string]: any
135
+ }
136
+
137
+ export function rimelightPackConfig(options: RimelightPackOptions = {}): any {
138
+ const { entry, autoEntry, pack = {}, autoExternal = true, ...rest } = options
139
+
140
+ let resolvedEntry: string[] = ["src/index.ts"]
141
+
142
+ if (Array.isArray(entry)) {
143
+ resolvedEntry = entry
144
+ } else if (entry === "auto" || autoEntry) {
145
+ const dir = typeof autoEntry === "string" ? autoEntry : "src"
146
+ const discovered = findSourceEntries(dir)
147
+ if (discovered.length > 0) {
148
+ resolvedEntry = discovered
149
+ }
150
+ } else if (typeof entry === "string") {
151
+ resolvedEntry = [entry]
152
+ }
153
+
154
+ const discoveredExternals = autoExternal ? findPackageExternals() : []
155
+ const userExternal = pack["external"] || []
156
+ const userExternalList = Array.isArray(userExternal) ? userExternal : [userExternal]
157
+ const userNeverBundle = (pack["deps"] as Record<string, any> | undefined)?.["neverBundle"] || []
158
+ const userNeverBundleList = Array.isArray(userNeverBundle) ? userNeverBundle : [userNeverBundle]
159
+ const mergedExternal = Array.from(
160
+ new Set([...userExternalList, ...userNeverBundleList, ...discoveredExternals])
161
+ ).filter(Boolean)
162
+
30
163
  return {
31
164
  ...rimelightConfig(),
32
165
  ...rest,
33
166
  pack: {
34
- entry,
167
+ entry: resolvedEntry,
35
168
  dts: true,
169
+ ...(mergedExternal.length > 0 ? { deps: { neverBundle: mergedExternal } } : {}),
36
170
  ...pack
37
171
  }
38
172
  }