@raystack/chronicle 0.1.0-canary.c5d277e → 0.1.0-canary.d9f273b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -55,7 +55,7 @@ async function createViteConfig(options) {
55
55
  remarkDirective
56
56
  ],
57
57
  rehypePlugins: [
58
- [rehypeShiki, { themes: { light: "github-light", dark: "github-dark" } }]
58
+ [rehypeShiki, { themes: { light: "github-light", dark: "github-dark" }, defaultColor: false }]
59
59
  ],
60
60
  mdExtensions: [".md"],
61
61
  mdxExtensions: [".mdx"]
@@ -221,18 +221,123 @@ var init_dev = __esm(() => {
221
221
  init_vite_config();
222
222
  });
223
223
 
224
+ // src/server/adapters/vercel.ts
225
+ var exports_vercel = {};
226
+ __export(exports_vercel, {
227
+ buildVercelOutput: () => buildVercelOutput
228
+ });
229
+ import path7 from "path";
230
+ import fs3 from "fs/promises";
231
+ import { existsSync } from "fs";
232
+ import chalk5 from "chalk";
233
+ async function buildVercelOutput(options) {
234
+ const { distDir, contentDir, projectRoot } = options;
235
+ const outputDir = path7.resolve(projectRoot, ".vercel/output");
236
+ console.log(chalk5.gray("Generating Vercel output..."));
237
+ await fs3.rm(outputDir, { recursive: true, force: true });
238
+ const staticDir = path7.resolve(outputDir, "static");
239
+ const funcDir = path7.resolve(outputDir, "functions/index.func");
240
+ await fs3.mkdir(staticDir, { recursive: true });
241
+ await fs3.mkdir(funcDir, { recursive: true });
242
+ const clientDir = path7.resolve(distDir, "client");
243
+ await copyDir(clientDir, staticDir);
244
+ console.log(chalk5.gray(" Copied client assets to static/"));
245
+ if (existsSync(contentDir)) {
246
+ await copyContentAssets(contentDir, staticDir);
247
+ console.log(chalk5.gray(" Copied content assets to static/"));
248
+ }
249
+ const serverDir = path7.resolve(distDir, "server");
250
+ await copyDir(serverDir, funcDir);
251
+ console.log(chalk5.gray(" Copied server bundle to functions/"));
252
+ const templateSrc = path7.resolve(clientDir, "src/server/index.html");
253
+ await fs3.copyFile(templateSrc, path7.resolve(funcDir, "index.html"));
254
+ await fs3.writeFile(path7.resolve(funcDir, ".vc-config.json"), JSON.stringify({
255
+ runtime: "nodejs22.x",
256
+ handler: "entry-vercel.js",
257
+ launcherType: "Nodejs"
258
+ }, null, 2));
259
+ await fs3.writeFile(path7.resolve(outputDir, "config.json"), JSON.stringify({
260
+ version: 3,
261
+ routes: [
262
+ { handle: "filesystem" },
263
+ { src: "/(.*)", dest: "/index" }
264
+ ]
265
+ }, null, 2));
266
+ console.log(chalk5.green("Vercel output generated →"), outputDir);
267
+ }
268
+ async function copyDir(src, dest) {
269
+ await fs3.mkdir(dest, { recursive: true });
270
+ const entries = await fs3.readdir(src, { withFileTypes: true });
271
+ for (const entry of entries) {
272
+ const srcPath = path7.join(src, entry.name);
273
+ const destPath = path7.join(dest, entry.name);
274
+ if (entry.isDirectory()) {
275
+ await copyDir(srcPath, destPath);
276
+ } else {
277
+ await fs3.copyFile(srcPath, destPath);
278
+ }
279
+ }
280
+ }
281
+ async function copyContentAssets(contentDir, staticDir) {
282
+ const entries = await fs3.readdir(contentDir, { withFileTypes: true });
283
+ for (const entry of entries) {
284
+ const srcPath = path7.join(contentDir, entry.name);
285
+ if (entry.isDirectory()) {
286
+ const destSubDir = path7.join(staticDir, entry.name);
287
+ await copyContentAssetsRecursive(srcPath, destSubDir);
288
+ } else {
289
+ const ext = path7.extname(entry.name).toLowerCase();
290
+ if (CONTENT_EXTENSIONS.has(ext)) {
291
+ await fs3.copyFile(srcPath, path7.join(staticDir, entry.name));
292
+ }
293
+ }
294
+ }
295
+ }
296
+ async function copyContentAssetsRecursive(srcDir, destDir) {
297
+ const entries = await fs3.readdir(srcDir, { withFileTypes: true });
298
+ for (const entry of entries) {
299
+ const srcPath = path7.join(srcDir, entry.name);
300
+ if (entry.isDirectory()) {
301
+ await copyContentAssetsRecursive(srcPath, path7.join(destDir, entry.name));
302
+ } else {
303
+ const ext = path7.extname(entry.name).toLowerCase();
304
+ if (CONTENT_EXTENSIONS.has(ext)) {
305
+ await fs3.mkdir(destDir, { recursive: true });
306
+ await fs3.copyFile(srcPath, path7.join(destDir, entry.name));
307
+ }
308
+ }
309
+ }
310
+ }
311
+ var CONTENT_EXTENSIONS;
312
+ var init_vercel = __esm(() => {
313
+ CONTENT_EXTENSIONS = new Set([
314
+ ".png",
315
+ ".jpg",
316
+ ".jpeg",
317
+ ".gif",
318
+ ".svg",
319
+ ".webp",
320
+ ".ico",
321
+ ".pdf",
322
+ ".json",
323
+ ".yaml",
324
+ ".yml",
325
+ ".txt"
326
+ ]);
327
+ });
328
+
224
329
  // src/server/prod.ts
225
330
  var exports_prod = {};
226
331
  __export(exports_prod, {
227
332
  startProdServer: () => startProdServer
228
333
  });
229
- import path8 from "path";
230
- import chalk6 from "chalk";
334
+ import path9 from "path";
335
+ import chalk7 from "chalk";
231
336
  async function startProdServer(options) {
232
337
  const { port, distDir } = options;
233
- const serverEntry = path8.resolve(distDir, "server/entry-prod.js");
338
+ const serverEntry = path9.resolve(distDir, "server/entry-prod.js");
234
339
  const { startServer } = await import(serverEntry);
235
- console.log(chalk6.cyan("Starting production server..."));
340
+ console.log(chalk7.cyan("Starting production server..."));
236
341
  return startServer({ port, distDir });
237
342
  }
238
343
  var init_prod = () => {};
@@ -442,78 +547,87 @@ var devCommand = new Command2("dev").description("Start development server").opt
442
547
 
443
548
  // src/cli/commands/build.ts
444
549
  import { Command as Command3 } from "commander";
445
- import path7 from "path";
446
- import chalk5 from "chalk";
447
- var buildCommand = new Command3("build").description("Build for production").option("-c, --content <path>", "Content directory").option("-o, --outDir <path>", "Output directory", "dist").action(async (options) => {
550
+ import path8 from "path";
551
+ import chalk6 from "chalk";
552
+ var buildCommand = new Command3("build").description("Build for production").option("-c, --content <path>", "Content directory").option("-o, --outDir <path>", "Output directory", "dist").option("--adapter <adapter>", "Deploy adapter (vercel)").action(async (options) => {
448
553
  const contentDir = resolveContentDir(options.content);
449
- const outDir = path7.resolve(options.outDir);
554
+ const outDir = path8.resolve(options.outDir);
450
555
  process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
451
556
  process.env.CHRONICLE_CONTENT_DIR = contentDir;
452
- console.log(chalk5.cyan("Building for production..."));
557
+ console.log(chalk6.cyan("Building for production..."));
453
558
  const { build } = await import("vite");
454
559
  const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
455
560
  const baseConfig = await createViteConfig2({ root: PACKAGE_ROOT, contentDir });
456
- console.log(chalk5.gray("Building client..."));
561
+ console.log(chalk6.gray("Building client..."));
457
562
  await build({
458
563
  ...baseConfig,
459
564
  build: {
460
- outDir: path7.join(outDir, "client"),
565
+ outDir: path8.join(outDir, "client"),
461
566
  ssrManifest: true,
462
567
  rolldownOptions: {
463
- input: path7.resolve(PACKAGE_ROOT, "src/server/index.html")
568
+ input: path8.resolve(PACKAGE_ROOT, "src/server/index.html")
464
569
  }
465
570
  }
466
571
  });
467
- console.log(chalk5.gray("Building server..."));
572
+ const serverEntry = options.adapter === "vercel" ? path8.resolve(PACKAGE_ROOT, "src/server/entry-vercel.ts") : path8.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts");
573
+ console.log(chalk6.gray("Building server..."));
468
574
  await build({
469
575
  ...baseConfig,
470
576
  ssr: {
471
577
  noExternal: true
472
578
  },
473
579
  build: {
474
- outDir: path7.join(outDir, "server"),
475
- ssr: path7.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts")
580
+ outDir: path8.join(outDir, "server"),
581
+ ssr: serverEntry
476
582
  }
477
583
  });
478
- console.log(chalk5.green("Build complete →"), outDir);
584
+ console.log(chalk6.green("Build complete →"), outDir);
585
+ if (options.adapter === "vercel") {
586
+ const { buildVercelOutput: buildVercelOutput2 } = await Promise.resolve().then(() => (init_vercel(), exports_vercel));
587
+ await buildVercelOutput2({
588
+ distDir: outDir,
589
+ contentDir,
590
+ projectRoot: process.cwd()
591
+ });
592
+ }
479
593
  });
480
594
 
481
595
  // src/cli/commands/start.ts
482
596
  import { Command as Command4 } from "commander";
483
- import path9 from "path";
484
- import chalk7 from "chalk";
597
+ import path10 from "path";
598
+ import chalk8 from "chalk";
485
599
  var startCommand = new Command4("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("-c, --content <path>", "Content directory").option("-d, --dist <path>", "Dist directory", "dist").action(async (options) => {
486
600
  const contentDir = resolveContentDir(options.content);
487
601
  const port = parseInt(options.port, 10);
488
- const distDir = path9.resolve(options.dist);
602
+ const distDir = path10.resolve(options.dist);
489
603
  process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
490
604
  process.env.CHRONICLE_CONTENT_DIR = contentDir;
491
- console.log(chalk7.cyan("Starting production server..."));
605
+ console.log(chalk8.cyan("Starting production server..."));
492
606
  const { startProdServer: startProdServer2 } = await Promise.resolve().then(() => (init_prod(), exports_prod));
493
607
  await startProdServer2({ port, root: PACKAGE_ROOT, distDir });
494
608
  });
495
609
 
496
610
  // src/cli/commands/serve.ts
497
611
  import { Command as Command5 } from "commander";
498
- import path10 from "path";
499
- import chalk8 from "chalk";
612
+ import path11 from "path";
613
+ import chalk9 from "chalk";
500
614
  var serveCommand = new Command5("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("-c, --content <path>", "Content directory").option("-o, --outDir <path>", "Output directory", "dist").action(async (options) => {
501
615
  const contentDir = resolveContentDir(options.content);
502
616
  const port = parseInt(options.port, 10);
503
- const outDir = path10.resolve(options.outDir);
617
+ const outDir = path11.resolve(options.outDir);
504
618
  process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
505
619
  process.env.CHRONICLE_CONTENT_DIR = contentDir;
506
- console.log(chalk8.cyan("Building for production..."));
620
+ console.log(chalk9.cyan("Building for production..."));
507
621
  const { build } = await import("vite");
508
622
  const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
509
623
  const baseConfig = await createViteConfig2({ root: PACKAGE_ROOT, contentDir });
510
624
  await build({
511
625
  ...baseConfig,
512
626
  build: {
513
- outDir: path10.join(outDir, "client"),
627
+ outDir: path11.join(outDir, "client"),
514
628
  ssrManifest: true,
515
629
  rolldownOptions: {
516
- input: path10.resolve(PACKAGE_ROOT, "src/server/index.html")
630
+ input: path11.resolve(PACKAGE_ROOT, "src/server/index.html")
517
631
  }
518
632
  }
519
633
  });
@@ -523,11 +637,11 @@ var serveCommand = new Command5("serve").description("Build and start production
523
637
  noExternal: true
524
638
  },
525
639
  build: {
526
- outDir: path10.join(outDir, "server"),
527
- ssr: path10.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts")
640
+ outDir: path11.join(outDir, "server"),
641
+ ssr: path11.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts")
528
642
  }
529
643
  });
530
- console.log(chalk8.cyan("Starting production server..."));
644
+ console.log(chalk9.cyan("Starting production server..."));
531
645
  const { startProdServer: startProdServer2 } = await Promise.resolve().then(() => (init_prod(), exports_prod));
532
646
  await startProdServer2({ port, root: PACKAGE_ROOT, distDir: outDir });
533
647
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raystack/chronicle",
3
- "version": "0.1.0-canary.c5d277e",
3
+ "version": "0.1.0-canary.d9f273b",
4
4
  "description": "Config-driven documentation framework",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -8,6 +8,7 @@ export const buildCommand = new Command('build')
8
8
  .description('Build for production')
9
9
  .option('-c, --content <path>', 'Content directory')
10
10
  .option('-o, --outDir <path>', 'Output directory', 'dist')
11
+ .option('--adapter <adapter>', 'Deploy adapter (vercel)')
11
12
  .action(async (options) => {
12
13
  const contentDir = resolveContentDir(options.content)
13
14
  const outDir = path.resolve(options.outDir)
@@ -35,7 +36,11 @@ export const buildCommand = new Command('build')
35
36
  },
36
37
  })
37
38
 
38
- // Build server bundle (noExternal: true to bundle all deps for portability)
39
+ // Build server bundle
40
+ const serverEntry = options.adapter === 'vercel'
41
+ ? path.resolve(PACKAGE_ROOT, 'src/server/entry-vercel.ts')
42
+ : path.resolve(PACKAGE_ROOT, 'src/server/entry-prod.ts')
43
+
39
44
  console.log(chalk.gray('Building server...'))
40
45
  await build({
41
46
  ...baseConfig,
@@ -44,9 +49,19 @@ export const buildCommand = new Command('build')
44
49
  },
45
50
  build: {
46
51
  outDir: path.join(outDir, 'server'),
47
- ssr: path.resolve(PACKAGE_ROOT, 'src/server/entry-prod.ts'),
52
+ ssr: serverEntry,
48
53
  },
49
54
  })
50
55
 
51
56
  console.log(chalk.green('Build complete →'), outDir)
57
+
58
+ // Run Vercel adapter post-build
59
+ if (options.adapter === 'vercel') {
60
+ const { buildVercelOutput } = await import('@/server/adapters/vercel')
61
+ await buildVercelOutput({
62
+ distDir: outDir,
63
+ contentDir,
64
+ projectRoot: process.cwd(),
65
+ })
66
+ }
52
67
  })
@@ -0,0 +1,127 @@
1
+ import path from 'path'
2
+ import fs from 'fs/promises'
3
+ import { existsSync } from 'fs'
4
+ import chalk from 'chalk'
5
+
6
+ interface VercelAdapterOptions {
7
+ distDir: string
8
+ contentDir: string
9
+ projectRoot: string
10
+ }
11
+
12
+ const CONTENT_EXTENSIONS = new Set([
13
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
14
+ '.pdf', '.json', '.yaml', '.yml', '.txt',
15
+ ])
16
+
17
+ export async function buildVercelOutput(options: VercelAdapterOptions) {
18
+ const { distDir, contentDir, projectRoot } = options
19
+ const outputDir = path.resolve(projectRoot, '.vercel/output')
20
+
21
+ console.log(chalk.gray('Generating Vercel output...'))
22
+
23
+ // Clean previous output
24
+ await fs.rm(outputDir, { recursive: true, force: true })
25
+
26
+ // Create output directories
27
+ const staticDir = path.resolve(outputDir, 'static')
28
+ const funcDir = path.resolve(outputDir, 'functions/index.func')
29
+ await fs.mkdir(staticDir, { recursive: true })
30
+ await fs.mkdir(funcDir, { recursive: true })
31
+
32
+ // 1. Copy client assets → .vercel/output/static/
33
+ const clientDir = path.resolve(distDir, 'client')
34
+ await copyDir(clientDir, staticDir)
35
+ console.log(chalk.gray(' Copied client assets to static/'))
36
+
37
+ // 2. Copy content dir assets (images, etc.) → .vercel/output/static/
38
+ if (existsSync(contentDir)) {
39
+ await copyContentAssets(contentDir, staticDir)
40
+ console.log(chalk.gray(' Copied content assets to static/'))
41
+ }
42
+
43
+ // 3. Copy server bundle → .vercel/output/functions/index.func/
44
+ const serverDir = path.resolve(distDir, 'server')
45
+ await copyDir(serverDir, funcDir)
46
+ console.log(chalk.gray(' Copied server bundle to functions/'))
47
+
48
+ // 4. Copy HTML template into function dir (not accessible from static/ at runtime)
49
+ const templateSrc = path.resolve(clientDir, 'src/server/index.html')
50
+ await fs.copyFile(templateSrc, path.resolve(funcDir, 'index.html'))
51
+
52
+ // 5. Write .vc-config.json
53
+ await fs.writeFile(
54
+ path.resolve(funcDir, '.vc-config.json'),
55
+ JSON.stringify({
56
+ runtime: 'nodejs22.x',
57
+ handler: 'entry-vercel.js',
58
+ launcherType: 'Nodejs',
59
+ }, null, 2),
60
+ )
61
+
62
+ // 6. Write config.json
63
+ await fs.writeFile(
64
+ path.resolve(outputDir, 'config.json'),
65
+ JSON.stringify({
66
+ version: 3,
67
+ routes: [
68
+ { handle: 'filesystem' },
69
+ { src: '/(.*)', dest: '/index' },
70
+ ],
71
+ }, null, 2),
72
+ )
73
+
74
+ console.log(chalk.green('Vercel output generated →'), outputDir)
75
+ }
76
+
77
+ async function copyDir(src: string, dest: string) {
78
+ await fs.mkdir(dest, { recursive: true })
79
+ const entries = await fs.readdir(src, { withFileTypes: true })
80
+
81
+ for (const entry of entries) {
82
+ const srcPath = path.join(src, entry.name)
83
+ const destPath = path.join(dest, entry.name)
84
+
85
+ if (entry.isDirectory()) {
86
+ await copyDir(srcPath, destPath)
87
+ } else {
88
+ await fs.copyFile(srcPath, destPath)
89
+ }
90
+ }
91
+ }
92
+
93
+ async function copyContentAssets(contentDir: string, staticDir: string) {
94
+ const entries = await fs.readdir(contentDir, { withFileTypes: true })
95
+
96
+ for (const entry of entries) {
97
+ const srcPath = path.join(contentDir, entry.name)
98
+
99
+ if (entry.isDirectory()) {
100
+ const destSubDir = path.join(staticDir, entry.name)
101
+ await copyContentAssetsRecursive(srcPath, destSubDir)
102
+ } else {
103
+ const ext = path.extname(entry.name).toLowerCase()
104
+ if (CONTENT_EXTENSIONS.has(ext)) {
105
+ await fs.copyFile(srcPath, path.join(staticDir, entry.name))
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ async function copyContentAssetsRecursive(srcDir: string, destDir: string) {
112
+ const entries = await fs.readdir(srcDir, { withFileTypes: true })
113
+
114
+ for (const entry of entries) {
115
+ const srcPath = path.join(srcDir, entry.name)
116
+
117
+ if (entry.isDirectory()) {
118
+ await copyContentAssetsRecursive(srcPath, path.join(destDir, entry.name))
119
+ } else {
120
+ const ext = path.extname(entry.name).toLowerCase()
121
+ if (CONTENT_EXTENSIONS.has(ext)) {
122
+ await fs.mkdir(destDir, { recursive: true })
123
+ await fs.copyFile(srcPath, path.join(destDir, entry.name))
124
+ }
125
+ }
126
+ }
127
+ }
@@ -3,16 +3,22 @@ import { createServer } from 'http'
3
3
  import { readFileSync, createReadStream } from 'fs'
4
4
  import fsPromises from 'fs/promises'
5
5
  import path from 'path'
6
- import React from 'react'
7
6
  import { render } from './entry-server'
8
7
  import { matchRoute } from './router'
9
8
  import { loadConfig } from '@/lib/config'
10
9
  import { loadApiSpecs } from '@/lib/openapi'
11
10
  import { getPage, loadPageComponent, buildPageTree } from '@/lib/source'
12
- import { mdxComponents } from '@/components/mdx'
11
+ import { handleRequest } from './request-handler'
13
12
 
14
13
  export { render, matchRoute, loadConfig, loadApiSpecs, getPage, loadPageComponent, buildPageTree }
15
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
+
16
22
  export async function startServer(options: { port: number; distDir: string }) {
17
23
  const { port, distDir } = options
18
24
 
@@ -23,19 +29,17 @@ export async function startServer(options: { port: number; distDir: string }) {
23
29
  const sirv = (await import('sirv')).default
24
30
  const assets = sirv(clientDir, { gzip: true })
25
31
 
32
+ const baseUrl = `http://localhost:${port}`
33
+
26
34
  const server = createServer(async (req, res) => {
27
35
  const url = req.url || '/'
28
36
 
29
37
  try {
30
- // API routes
31
- const routeHandler = matchRoute(new URL(url, `http://localhost:${port}`).href)
38
+ // API routes — handled by shared request handler
39
+ const routeHandler = matchRoute(new URL(url, baseUrl).href)
32
40
  if (routeHandler) {
33
- const request = new Request(new URL(url, `http://localhost:${port}`))
34
- const response = await routeHandler(request)
35
- res.statusCode = response.status
36
- response.headers.forEach((value: string, key: string) => res.setHeader(key, value))
37
- const body = await response.text()
38
- res.end(body)
41
+ const response = await routeHandler(new Request(new URL(url, baseUrl)))
42
+ await writeResponse(res, response)
39
43
  return
40
44
  }
41
45
 
@@ -67,43 +71,9 @@ export async function startServer(options: { port: number; distDir: string }) {
67
71
  })
68
72
  if (assetHandled) return
69
73
 
70
- // Resolve page data
71
- const pathname = new URL(url, `http://localhost:${port}`).pathname
72
- const slug = pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean)
73
-
74
- const config = loadConfig()
75
- const apiSpecs = config.api?.length ? loadApiSpecs(config.api) : []
76
-
77
- const [tree, sourcePage] = await Promise.all([
78
- buildPageTree(),
79
- getPage(slug),
80
- ])
81
-
82
- let pageData = null
83
- let embeddedData: any = { config, tree, slug, frontmatter: null, filePath: null }
84
-
85
- if (sourcePage) {
86
- const component = await loadPageComponent(sourcePage)
87
- pageData = {
88
- slug,
89
- frontmatter: sourcePage.frontmatter,
90
- content: component ? React.createElement(component, { components: mdxComponents }) : null,
91
- }
92
- embeddedData.frontmatter = sourcePage.frontmatter
93
- embeddedData.filePath = sourcePage.filePath
94
- }
95
-
96
- // SSR render
97
- const html = render(url, { config, tree, page: pageData, apiSpecs })
98
-
99
- const dataScript = `<script>window.__PAGE_DATA__ = ${JSON.stringify(embeddedData)}</script>`
100
- const finalHtml = template
101
- .replace('<!--head-outlet-->', `<!--head-outlet-->${dataScript}`)
102
- .replace('<!--ssr-outlet-->', html)
103
-
104
- res.setHeader('Content-Type', 'text/html')
105
- res.statusCode = 200
106
- res.end(finalHtml)
74
+ // SSR render — handled by shared request handler
75
+ const response = await handleRequest(url, { template, baseUrl })
76
+ await writeResponse(res, response)
107
77
  } catch (e) {
108
78
  console.error(e)
109
79
  res.statusCode = 500
@@ -0,0 +1,26 @@
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
+ }
@@ -0,0 +1,63 @@
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
+ }
@@ -41,7 +41,7 @@ export async function createViteConfig(options: ViteConfigOptions): Promise<Inli
41
41
  remarkDirective,
42
42
  ],
43
43
  rehypePlugins: [
44
- [rehypeShiki, { themes: { light: 'github-light', dark: 'github-dark' } }],
44
+ [rehypeShiki, { themes: { light: 'github-light', dark: 'github-dark' }, defaultColor: false }],
45
45
  ],
46
46
  mdExtensions: ['.md'],
47
47
  mdxExtensions: ['.mdx'],
@@ -38,18 +38,22 @@
38
38
  margin-bottom: var(--rs-space-3);
39
39
  }
40
40
 
41
- .content :global(pre code span) {
42
- color: var(--shiki-light);
41
+ .content :global(pre) {
42
+ background-color: var(--shiki-light-bg, #fff);
43
43
  }
44
44
 
45
- :global([data-theme="dark"]) .content :global(pre code span) {
46
- color: var(--shiki-dark);
45
+ .content :global(pre code span) {
46
+ color: var(--shiki-light);
47
47
  }
48
48
 
49
49
  :global([data-theme="dark"]) .content :global(pre) {
50
50
  background-color: var(--shiki-dark-bg, #24292e);
51
51
  }
52
52
 
53
+ :global([data-theme="dark"]) .content :global(pre code span) {
54
+ color: var(--shiki-dark);
55
+ }
56
+
53
57
  .content img {
54
58
  max-width: 100%;
55
59
  height: auto;