@raystack/chronicle 0.1.0-canary.a638730 → 0.1.0-canary.bd7f9af

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/cli/index.js +204 -530
  2. package/package.json +13 -9
  3. package/src/cli/commands/build.ts +30 -63
  4. package/src/cli/commands/dev.ts +24 -13
  5. package/src/cli/commands/init.ts +38 -123
  6. package/src/cli/commands/serve.ts +35 -50
  7. package/src/cli/commands/start.ts +20 -16
  8. package/src/cli/index.ts +14 -14
  9. package/src/cli/utils/config.ts +25 -26
  10. package/src/cli/utils/index.ts +3 -2
  11. package/src/cli/utils/resolve.ts +7 -3
  12. package/src/cli/utils/scaffold.ts +14 -16
  13. package/src/components/mdx/details.module.css +0 -2
  14. package/src/components/mdx/image.tsx +5 -20
  15. package/src/components/mdx/index.tsx +3 -3
  16. package/src/components/mdx/link.tsx +24 -20
  17. package/src/components/ui/breadcrumbs.tsx +8 -42
  18. package/src/components/ui/footer.tsx +2 -3
  19. package/src/components/ui/search.tsx +116 -71
  20. package/src/lib/api-routes.ts +6 -8
  21. package/src/lib/config.ts +31 -29
  22. package/src/lib/get-llm-text.ts +10 -0
  23. package/src/lib/head.tsx +26 -22
  24. package/src/lib/openapi.ts +8 -8
  25. package/src/lib/page-context.tsx +87 -58
  26. package/src/lib/source.ts +117 -116
  27. package/src/pages/ApiLayout.tsx +22 -18
  28. package/src/pages/ApiPage.tsx +32 -27
  29. package/src/pages/DocsLayout.tsx +7 -7
  30. package/src/pages/DocsPage.tsx +11 -11
  31. package/src/pages/NotFound.tsx +11 -4
  32. package/src/server/App.tsx +35 -27
  33. package/src/server/api/apis-proxy.ts +69 -0
  34. package/src/server/api/health.ts +5 -0
  35. package/src/server/api/page/[...slug].ts +26 -0
  36. package/src/server/api/search.ts +170 -0
  37. package/src/server/api/specs.ts +9 -0
  38. package/src/server/build-search-index.ts +117 -0
  39. package/src/server/entry-client.tsx +66 -58
  40. package/src/server/entry-server.tsx +104 -35
  41. package/src/server/routes/llms.txt.ts +61 -0
  42. package/src/server/routes/og.tsx +75 -0
  43. package/src/server/routes/robots.txt.ts +11 -0
  44. package/src/server/routes/sitemap.xml.ts +40 -0
  45. package/src/server/utils/safe-path.ts +17 -0
  46. package/src/server/vite-config.ts +79 -49
  47. package/src/themes/default/Layout.tsx +78 -47
  48. package/src/themes/default/Page.module.css +0 -16
  49. package/src/themes/default/Page.tsx +9 -11
  50. package/src/themes/default/Toc.tsx +25 -39
  51. package/src/themes/default/index.ts +7 -9
  52. package/src/themes/paper/ChapterNav.tsx +63 -43
  53. package/src/themes/paper/Layout.module.css +1 -1
  54. package/src/themes/paper/Layout.tsx +24 -12
  55. package/src/themes/paper/Page.module.css +16 -4
  56. package/src/themes/paper/Page.tsx +56 -62
  57. package/src/themes/paper/ReadingProgress.tsx +160 -139
  58. package/src/themes/paper/index.ts +5 -5
  59. package/src/themes/registry.ts +7 -7
  60. package/src/types/content.ts +5 -21
  61. package/src/types/globals.d.ts +3 -0
  62. package/src/types/theme.ts +4 -3
  63. package/src/cli/__tests__/config.test.ts +0 -25
  64. package/src/cli/__tests__/scaffold.test.ts +0 -10
  65. package/src/pages/__tests__/head.test.tsx +0 -57
  66. package/src/server/__tests__/entry-server.test.tsx +0 -35
  67. package/src/server/__tests__/handlers.test.ts +0 -77
  68. package/src/server/__tests__/og.test.ts +0 -23
  69. package/src/server/__tests__/router.test.ts +0 -72
  70. package/src/server/__tests__/vite-config.test.ts +0 -25
  71. package/src/server/adapters/vercel.ts +0 -133
  72. package/src/server/dev.ts +0 -156
  73. package/src/server/entry-prod.ts +0 -97
  74. package/src/server/entry-vercel.ts +0 -26
  75. package/src/server/handlers/apis-proxy.ts +0 -52
  76. package/src/server/handlers/health.ts +0 -3
  77. package/src/server/handlers/llms.ts +0 -58
  78. package/src/server/handlers/og.ts +0 -87
  79. package/src/server/handlers/robots.ts +0 -11
  80. package/src/server/handlers/search.ts +0 -140
  81. package/src/server/handlers/sitemap.ts +0 -39
  82. package/src/server/handlers/specs.ts +0 -9
  83. package/src/server/index.html +0 -12
  84. package/src/server/prod.ts +0 -18
  85. package/src/server/request-handler.ts +0 -63
  86. package/src/server/router.ts +0 -42
  87. package/src/themes/default/font.ts +0 -4
@@ -1,97 +0,0 @@
1
- // Production server entry — built by Vite, loaded by prod.ts at runtime
2
- import { createServer } from 'http'
3
- import { readFileSync, createReadStream } from 'fs'
4
- import fsPromises from 'fs/promises'
5
- import path from 'path'
6
- import { render } from './entry-server'
7
- import { matchRoute } from './router'
8
- import { loadConfig } from '@/lib/config'
9
- import { loadApiSpecs } from '@/lib/openapi'
10
- import { getPage, loadPageComponent, buildPageTree } from '@/lib/source'
11
- import { handleRequest } from './request-handler'
12
-
13
- export { render, matchRoute, loadConfig, loadApiSpecs, getPage, loadPageComponent, buildPageTree }
14
-
15
- async function writeResponse(res: import('http').ServerResponse, response: Response) {
16
- res.statusCode = response.status
17
- response.headers.forEach((value: string, key: string) => res.setHeader(key, value))
18
- const body = await response.text()
19
- res.end(body)
20
- }
21
-
22
- export async function startServer(options: { port: number; distDir: string }) {
23
- const { port, distDir } = options
24
-
25
- const clientDir = path.resolve(distDir, 'client')
26
- const templatePath = path.resolve(clientDir, 'src/server/index.html')
27
- const template = readFileSync(templatePath, 'utf-8')
28
-
29
- const sirv = (await import('sirv')).default
30
- const assets = sirv(clientDir, { gzip: true })
31
-
32
- const baseUrl = `http://localhost:${port}`
33
-
34
- const server = createServer(async (req, res) => {
35
- const url = req.url || '/'
36
-
37
- try {
38
- // API routes — handled by shared request handler
39
- const routeHandler = matchRoute(new URL(url, baseUrl).href)
40
- if (routeHandler) {
41
- const response = await routeHandler(new Request(new URL(url, baseUrl)))
42
- await writeResponse(res, response)
43
- return
44
- }
45
-
46
- // Serve static files from content dir (skip .md/.mdx)
47
- const contentDir = process.env.CHRONICLE_CONTENT_DIR || process.cwd()
48
- const contentFile = path.join(contentDir, decodeURIComponent(url.split('?')[0]))
49
- if (!url.endsWith('.md') && !url.endsWith('.mdx')) {
50
- try {
51
- const stat = await fsPromises.stat(contentFile)
52
- if (stat.isFile()) {
53
- const ext = path.extname(contentFile).toLowerCase()
54
- const mimeTypes: Record<string, string> = {
55
- '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
56
- '.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp',
57
- '.ico': 'image/x-icon', '.pdf': 'application/pdf', '.json': 'application/json',
58
- '.yaml': 'text/yaml', '.yml': 'text/yaml', '.txt': 'text/plain',
59
- }
60
- res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream')
61
- createReadStream(contentFile).pipe(res)
62
- return
63
- }
64
- } catch { /* fall through */ }
65
- }
66
-
67
- // Static assets from dist/client
68
- const assetHandled = await new Promise<boolean>((resolve) => {
69
- assets(req, res, () => resolve(false))
70
- res.on('close', () => resolve(true))
71
- })
72
- if (assetHandled) return
73
-
74
- // SSR render — handled by shared request handler
75
- const response = await handleRequest(url, { template, baseUrl })
76
- await writeResponse(res, response)
77
- } catch (e) {
78
- console.error(e)
79
- res.statusCode = 500
80
- res.end((e as Error).message)
81
- }
82
- })
83
-
84
- server.listen(port, () => {
85
- console.log(`\n Chronicle production server running at:`)
86
- console.log(` http://localhost:${port}\n`)
87
- })
88
-
89
- const shutdown = () => {
90
- server.close()
91
- process.exit(0)
92
- }
93
- process.on('SIGINT', shutdown)
94
- process.on('SIGTERM', shutdown)
95
-
96
- return server
97
- }
@@ -1,26 +0,0 @@
1
- // Vercel serverless function entry — built by Vite, deployed as catch-all function
2
- import type { IncomingMessage, ServerResponse } from 'http'
3
- import { readFileSync } from 'fs'
4
- import path from 'path'
5
- import { handleRequest } from './request-handler'
6
-
7
- const templatePath = path.resolve(__dirname, 'index.html')
8
- const template = readFileSync(templatePath, 'utf-8')
9
-
10
- export default async function handler(req: IncomingMessage, res: ServerResponse) {
11
- const url = req.url || '/'
12
- const baseUrl = `https://${req.headers.host || 'localhost'}`
13
-
14
- try {
15
- const response = await handleRequest(url, { template, baseUrl })
16
-
17
- res.statusCode = response.status
18
- response.headers.forEach((value: string, key: string) => res.setHeader(key, value))
19
- const body = await response.text()
20
- res.end(body)
21
- } catch (e) {
22
- console.error(e)
23
- res.statusCode = 500
24
- res.end((e as Error).message)
25
- }
26
- }
@@ -1,52 +0,0 @@
1
- import { loadConfig } from '@/lib/config'
2
- import { loadApiSpecs } from '@/lib/openapi'
3
-
4
- export async function handleApisProxy(req: Request): Promise<Response> {
5
- if (req.method !== 'POST') {
6
- return Response.json({ error: 'Method not allowed' }, { status: 405 })
7
- }
8
-
9
- const { specName, method, path, headers, body } = await req.json()
10
-
11
- if (!specName || !method || !path) {
12
- return Response.json({ error: 'Missing specName, method, or path' }, { status: 400 })
13
- }
14
-
15
- const config = loadConfig()
16
- const specs = loadApiSpecs(config.api ?? [])
17
- const spec = specs.find((s) => s.name === specName)
18
-
19
- if (!spec) {
20
- return Response.json({ error: `Unknown spec: ${specName}` }, { status: 404 })
21
- }
22
-
23
- const url = spec.server.url + path
24
-
25
- try {
26
- const response = await fetch(url, {
27
- method,
28
- headers,
29
- body: body ? JSON.stringify(body) : undefined,
30
- })
31
-
32
- const contentType = response.headers.get('content-type') ?? ''
33
- const responseBody = contentType.includes('application/json')
34
- ? await response.json()
35
- : await response.text()
36
-
37
- return Response.json({
38
- status: response.status,
39
- statusText: response.statusText,
40
- body: responseBody,
41
- }, { status: response.status })
42
- } catch (error) {
43
- const message = error instanceof Error
44
- ? `${error.message}${error.cause ? `: ${(error.cause as Error).message}` : ''}`
45
- : 'Request failed'
46
- return Response.json({
47
- status: 502,
48
- statusText: 'Bad Gateway',
49
- body: `Could not reach ${url}\n${message}`,
50
- }, { status: 502 })
51
- }
52
- }
@@ -1,3 +0,0 @@
1
- export function handleHealth(): Response {
2
- return Response.json({ status: 'ok' })
3
- }
@@ -1,58 +0,0 @@
1
- import fs from 'fs/promises'
2
- import path from 'path'
3
- import matter from 'gray-matter'
4
- import { loadConfig } from '@/lib/config'
5
-
6
- function getContentDir(): string {
7
- return process.env.CHRONICLE_CONTENT_DIR || path.join(process.cwd(), 'content')
8
- }
9
-
10
- async function scanPages(): Promise<{ title: string; url: string }[]> {
11
- const contentDir = getContentDir()
12
- const pages: { title: string; url: string }[] = []
13
-
14
- async function scan(dir: string, prefix: string[] = []) {
15
- let entries
16
- try { entries = await fs.readdir(dir, { withFileTypes: true }) }
17
- catch { return }
18
-
19
- for (const entry of entries) {
20
- if (entry.name.startsWith('.') || entry.name === 'node_modules') continue
21
- const fullPath = path.join(dir, entry.name)
22
-
23
- if (entry.isDirectory()) {
24
- await scan(fullPath, [...prefix, entry.name])
25
- continue
26
- }
27
-
28
- if (!entry.name.endsWith('.mdx') && !entry.name.endsWith('.md')) continue
29
-
30
- const raw = await fs.readFile(fullPath, 'utf-8')
31
- const { data: fm } = matter(raw)
32
- const baseName = entry.name.replace(/\.(mdx|md)$/, '')
33
- const slugs = baseName === 'index' ? prefix : [...prefix, baseName]
34
- const url = slugs.length === 0 ? '/' : '/' + slugs.join('/')
35
-
36
- pages.push({ title: fm.title ?? baseName, url })
37
- }
38
- }
39
-
40
- await scan(contentDir)
41
- return pages
42
- }
43
-
44
- export async function handleLlms(): Promise<Response> {
45
- const config = loadConfig()
46
-
47
- if (!config.llms?.enabled) {
48
- return new Response('Not Found', { status: 404 })
49
- }
50
-
51
- const pages = await scanPages()
52
- const index = pages.map((p) => `- [${p.title}](${p.url})`).join('\n')
53
- const body = `# ${config.title}\n\n${config.description ?? ''}\n\n${index}`
54
-
55
- return new Response(body, {
56
- headers: { 'Content-Type': 'text/plain' },
57
- })
58
- }
@@ -1,87 +0,0 @@
1
- import satori from 'satori'
2
- import { loadConfig } from '@/lib/config'
3
-
4
- let fontData: ArrayBuffer | null = null
5
-
6
- async function loadFont(): Promise<ArrayBuffer> {
7
- if (fontData) return fontData
8
-
9
- try {
10
- const response = await fetch('https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiA.woff2')
11
- fontData = await response.arrayBuffer()
12
- } catch {
13
- // Fallback: create minimal valid font buffer
14
- fontData = new ArrayBuffer(0)
15
- }
16
-
17
- return fontData
18
- }
19
-
20
- export async function handleOg(req: Request): Promise<Response> {
21
- const url = new URL(req.url)
22
- const title = url.searchParams.get('title') ?? loadConfig().title
23
- const description = url.searchParams.get('description') ?? ''
24
- const siteName = loadConfig().title
25
-
26
- const font = await loadFont()
27
-
28
- const svg = await satori(
29
- {
30
- type: 'div',
31
- props: {
32
- style: {
33
- height: '100%',
34
- width: '100%',
35
- display: 'flex',
36
- flexDirection: 'column',
37
- justifyContent: 'center',
38
- padding: '60px 80px',
39
- backgroundColor: '#0a0a0a',
40
- color: '#fafafa',
41
- },
42
- children: [
43
- {
44
- type: 'div',
45
- props: {
46
- style: { fontSize: 24, color: '#888', marginBottom: 16 },
47
- children: siteName,
48
- },
49
- },
50
- {
51
- type: 'div',
52
- props: {
53
- style: { fontSize: 56, fontWeight: 700, lineHeight: 1.2, marginBottom: 24 },
54
- children: title,
55
- },
56
- },
57
- ...(description ? [{
58
- type: 'div',
59
- props: {
60
- style: { fontSize: 24, color: '#999', lineHeight: 1.4 },
61
- children: description,
62
- },
63
- }] : []),
64
- ],
65
- },
66
- },
67
- {
68
- width: 1200,
69
- height: 630,
70
- fonts: [
71
- {
72
- name: 'Inter',
73
- data: font,
74
- weight: 400,
75
- style: 'normal' as const,
76
- },
77
- ],
78
- },
79
- )
80
-
81
- return new Response(svg, {
82
- headers: {
83
- 'Content-Type': 'image/svg+xml',
84
- 'Cache-Control': 'public, max-age=86400',
85
- },
86
- })
87
- }
@@ -1,11 +0,0 @@
1
- import { loadConfig } from '@/lib/config'
2
-
3
- export function handleRobots(): Response {
4
- const config = loadConfig()
5
- const sitemap = config.url ? `\nSitemap: ${config.url}/sitemap.xml` : ''
6
- const body = `User-agent: *\nAllow: /${sitemap}`
7
-
8
- return new Response(body, {
9
- headers: { 'Content-Type': 'text/plain' },
10
- })
11
- }
@@ -1,140 +0,0 @@
1
- import fs from 'fs/promises'
2
- import path from 'path'
3
- import matter from 'gray-matter'
4
- import MiniSearch from 'minisearch'
5
- import type { OpenAPIV3 } from 'openapi-types'
6
- import { loadConfig } from '@/lib/config'
7
- import { loadApiSpecs, type ApiSpec } from '@/lib/openapi'
8
- import { getSpecSlug } from '@/lib/api-routes'
9
-
10
- interface SearchDocument {
11
- id: string
12
- url: string
13
- title: string
14
- content: string
15
- type: 'page' | 'api'
16
- }
17
-
18
- let searchIndex: MiniSearch<SearchDocument> | null = null
19
-
20
- function getContentDir(): string {
21
- return process.env.CHRONICLE_CONTENT_DIR || path.join(process.cwd(), 'content')
22
- }
23
-
24
- async function scanContent(): Promise<SearchDocument[]> {
25
- const contentDir = getContentDir()
26
- const docs: SearchDocument[] = []
27
-
28
- async function scan(dir: string, prefix: string[] = []) {
29
- let entries
30
- try { entries = await fs.readdir(dir, { withFileTypes: true }) }
31
- catch { return }
32
-
33
- for (const entry of entries) {
34
- if (entry.name.startsWith('.') || entry.name === 'node_modules') continue
35
- const fullPath = path.join(dir, entry.name)
36
-
37
- if (entry.isDirectory()) {
38
- await scan(fullPath, [...prefix, entry.name])
39
- continue
40
- }
41
-
42
- if (!entry.name.endsWith('.mdx') && !entry.name.endsWith('.md')) continue
43
-
44
- const raw = await fs.readFile(fullPath, 'utf-8')
45
- const { data: fm, content } = matter(raw)
46
- const baseName = entry.name.replace(/\.(mdx|md)$/, '')
47
- const slugs = baseName === 'index' ? prefix : [...prefix, baseName]
48
- const url = slugs.length === 0 ? '/' : '/' + slugs.join('/')
49
-
50
- docs.push({
51
- id: url,
52
- url,
53
- title: fm.title ?? baseName,
54
- content: content.slice(0, 5000),
55
- type: 'page',
56
- })
57
- }
58
- }
59
-
60
- await scan(contentDir)
61
- return docs
62
- }
63
-
64
- function buildApiDocs(): SearchDocument[] {
65
- const config = loadConfig()
66
- if (!config.api?.length) return []
67
-
68
- const docs: SearchDocument[] = []
69
- const specs = loadApiSpecs(config.api)
70
-
71
- for (const spec of specs) {
72
- const specSlug = getSpecSlug(spec)
73
- const paths = spec.document.paths ?? {}
74
- for (const [pathStr, pathItem] of Object.entries(paths)) {
75
- if (!pathItem) continue
76
- for (const method of ['get', 'post', 'put', 'delete', 'patch'] as const) {
77
- const op = pathItem[method] as OpenAPIV3.OperationObject | undefined
78
- if (!op?.operationId) continue
79
- const url = `/apis/${specSlug}/${encodeURIComponent(op.operationId)}`
80
- docs.push({
81
- id: url,
82
- url,
83
- title: `${method.toUpperCase()} ${op.summary ?? op.operationId}`,
84
- content: op.description ?? '',
85
- type: 'api',
86
- })
87
- }
88
- }
89
- }
90
-
91
- return docs
92
- }
93
-
94
- async function getIndex(): Promise<MiniSearch<SearchDocument>> {
95
- if (searchIndex) return searchIndex
96
-
97
- const [contentDocs, apiDocs] = await Promise.all([
98
- scanContent(),
99
- Promise.resolve(buildApiDocs()),
100
- ])
101
-
102
- searchIndex = new MiniSearch<SearchDocument>({
103
- fields: ['title', 'content'],
104
- storeFields: ['url', 'title', 'type'],
105
- searchOptions: {
106
- boost: { title: 2 },
107
- fuzzy: 0.2,
108
- prefix: true,
109
- },
110
- })
111
-
112
- searchIndex.addAll([...contentDocs, ...apiDocs])
113
- return searchIndex
114
- }
115
-
116
- export async function handleSearch(req: Request): Promise<Response> {
117
- const url = new URL(req.url)
118
- const query = url.searchParams.get('query') ?? ''
119
- const index = await getIndex()
120
-
121
- if (!query) {
122
- const contentDocs = await scanContent()
123
- const suggestions = contentDocs.slice(0, 8).map((d) => ({
124
- id: d.id,
125
- url: d.url,
126
- type: d.type,
127
- content: d.title,
128
- }))
129
- return Response.json(suggestions)
130
- }
131
-
132
- const results = index.search(query).map((r) => ({
133
- id: r.id,
134
- url: r.url,
135
- type: r.type,
136
- content: r.title,
137
- }))
138
-
139
- return Response.json(results)
140
- }
@@ -1,39 +0,0 @@
1
- import { loadConfig } from '@/lib/config'
2
- import { getPages } from '@/lib/source'
3
- import { loadApiSpecs } from '@/lib/openapi'
4
- import { buildApiRoutes } from '@/lib/api-routes'
5
-
6
- export async function handleSitemap(): Promise<Response> {
7
- const config = loadConfig()
8
- if (!config.url) {
9
- return new Response('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>', {
10
- headers: { 'Content-Type': 'application/xml' },
11
- })
12
- }
13
-
14
- const baseUrl = config.url.replace(/\/$/, '')
15
-
16
- const pages = await getPages()
17
- const docPages = pages.map((page) => {
18
- const lastmod = page.frontmatter.lastModified
19
- ? `<lastmod>${new Date(page.frontmatter.lastModified).toISOString()}</lastmod>`
20
- : ''
21
- return `<url><loc>${baseUrl}/${page.slugs.join('/')}</loc>${lastmod}</url>`
22
- })
23
-
24
- const apiPages = config.api?.length
25
- ? buildApiRoutes(loadApiSpecs(config.api)).map((route) =>
26
- `<url><loc>${baseUrl}/apis/${route.slug.join('/')}</loc></url>`
27
- )
28
- : []
29
-
30
- const xml = `<?xml version="1.0" encoding="UTF-8"?>
31
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
32
- <url><loc>${baseUrl}</loc></url>
33
- ${[...docPages, ...apiPages].join('\n')}
34
- </urlset>`
35
-
36
- return new Response(xml, {
37
- headers: { 'Content-Type': 'application/xml' },
38
- })
39
- }
@@ -1,9 +0,0 @@
1
- import { loadConfig } from '@/lib/config'
2
- import { loadApiSpecs } from '@/lib/openapi'
3
-
4
- export function handleSpecs(): Response {
5
- const config = loadConfig()
6
- const specs = config.api?.length ? loadApiSpecs(config.api) : []
7
-
8
- return Response.json(specs)
9
- }
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <!--head-outlet-->
7
- </head>
8
- <body>
9
- <div id="root"><!--ssr-outlet--></div>
10
- <script type="module" src="/src/server/entry-client.tsx"></script>
11
- </body>
12
- </html>
@@ -1,18 +0,0 @@
1
- import path from 'path'
2
- import chalk from 'chalk'
3
-
4
- export interface ProdServerOptions {
5
- port: number
6
- root: string
7
- distDir: string
8
- }
9
-
10
- export async function startProdServer(options: ProdServerOptions) {
11
- const { port, distDir } = options
12
-
13
- const serverEntry = path.resolve(distDir, 'server/entry-prod.js')
14
- const { startServer } = await import(serverEntry)
15
-
16
- console.log(chalk.cyan('Starting production server...'))
17
- return startServer({ port, distDir })
18
- }
@@ -1,63 +0,0 @@
1
- // Shared request handler for API routes + SSR rendering
2
- // Used by entry-prod.ts (Node) and entry-vercel.ts (Vercel)
3
- import React from 'react'
4
- import { render } from './entry-server'
5
- import { matchRoute } from './router'
6
- import { loadConfig } from '@/lib/config'
7
- import { loadApiSpecs } from '@/lib/openapi'
8
- import { getPage, loadPageComponent, buildPageTree } from '@/lib/source'
9
- import { mdxComponents } from '@/components/mdx'
10
-
11
- export interface RequestHandlerOptions {
12
- template: string
13
- baseUrl: string
14
- }
15
-
16
- export async function handleRequest(url: string, options: RequestHandlerOptions): Promise<Response> {
17
- const { template, baseUrl } = options
18
- const fullUrl = new URL(url, baseUrl).href
19
-
20
- // API routes
21
- const routeHandler = matchRoute(fullUrl)
22
- if (routeHandler) {
23
- return routeHandler(new Request(fullUrl))
24
- }
25
-
26
- // SSR render
27
- const pathname = new URL(url, baseUrl).pathname
28
- const slug = pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean)
29
-
30
- const config = loadConfig()
31
- const apiSpecs = config.api?.length ? loadApiSpecs(config.api) : []
32
-
33
- const [tree, sourcePage] = await Promise.all([
34
- buildPageTree(),
35
- getPage(slug),
36
- ])
37
-
38
- let pageData = null
39
- let embeddedData: any = { config, tree, slug, frontmatter: null, filePath: null }
40
-
41
- if (sourcePage) {
42
- const component = await loadPageComponent(sourcePage)
43
- pageData = {
44
- slug,
45
- frontmatter: sourcePage.frontmatter,
46
- content: component ? React.createElement(component, { components: mdxComponents }) : null,
47
- }
48
- embeddedData.frontmatter = sourcePage.frontmatter
49
- embeddedData.filePath = sourcePage.filePath
50
- }
51
-
52
- const html = render(url, { config, tree, page: pageData, apiSpecs })
53
-
54
- const dataScript = `<script>window.__PAGE_DATA__ = ${JSON.stringify(embeddedData)}</script>`
55
- const finalHtml = template
56
- .replace('<!--head-outlet-->', `<!--head-outlet-->${dataScript}`)
57
- .replace('<!--ssr-outlet-->', html)
58
-
59
- return new Response(finalHtml, {
60
- status: 200,
61
- headers: { 'Content-Type': 'text/html' },
62
- })
63
- }
@@ -1,42 +0,0 @@
1
- import { handleHealth } from './handlers/health'
2
- import { handleSearch } from './handlers/search'
3
- import { handleApisProxy } from './handlers/apis-proxy'
4
- import { handleOg } from './handlers/og'
5
- import { handleLlms } from './handlers/llms'
6
- import { handleSitemap } from './handlers/sitemap'
7
- import { handleRobots } from './handlers/robots'
8
- import { handleSpecs } from './handlers/specs'
9
-
10
- export type RouteHandler = (req: Request) => Response | Promise<Response>
11
-
12
- interface Route {
13
- pattern: URLPattern
14
- handler: RouteHandler
15
- }
16
-
17
- const routes: Route[] = []
18
-
19
- function addRoute(path: string, handler: RouteHandler) {
20
- routes.push({
21
- pattern: new URLPattern({ pathname: path }),
22
- handler,
23
- })
24
- }
25
-
26
- addRoute('/api/health', handleHealth)
27
- addRoute('/api/search', handleSearch)
28
- addRoute('/api/apis-proxy', handleApisProxy)
29
- addRoute('/api/specs', handleSpecs)
30
- addRoute('/og', handleOg)
31
- addRoute('/llms.txt', handleLlms)
32
- addRoute('/sitemap.xml', handleSitemap)
33
- addRoute('/robots.txt', handleRobots)
34
-
35
- export function matchRoute(url: string): RouteHandler | null {
36
- for (const route of routes) {
37
- if (route.pattern.test(url)) {
38
- return route.handler
39
- }
40
- }
41
- return null
42
- }
@@ -1,4 +0,0 @@
1
- export const inter = {
2
- className: 'chronicle-inter',
3
- style: { fontFamily: "'Inter', system-ui, -apple-system, sans-serif" },
4
- }