@raystack/chronicle 0.1.0-canary.5a730d4 → 0.1.0-canary.729744c
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 +151 -30
- package/package.json +1 -2
- package/src/cli/commands/build.ts +17 -2
- package/src/components/ui/search.tsx +1 -2
- package/src/server/adapters/vercel.ts +133 -0
- package/src/server/entry-prod.ts +17 -47
- package/src/server/entry-vercel.ts +26 -0
- package/src/server/request-handler.ts +63 -0
- package/src/server/vite-config.ts +7 -1
- package/src/themes/default/Page.module.css +8 -4
package/dist/cli/index.js
CHANGED
|
@@ -38,6 +38,12 @@ async function createViteConfig(options) {
|
|
|
38
38
|
alias: {
|
|
39
39
|
"@": path5.resolve(root, "src"),
|
|
40
40
|
"@content": contentDir
|
|
41
|
+
},
|
|
42
|
+
dedupe: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"]
|
|
43
|
+
},
|
|
44
|
+
server: {
|
|
45
|
+
fs: {
|
|
46
|
+
allow: [root, contentDir]
|
|
41
47
|
}
|
|
42
48
|
},
|
|
43
49
|
plugins: [
|
|
@@ -49,7 +55,7 @@ async function createViteConfig(options) {
|
|
|
49
55
|
remarkDirective
|
|
50
56
|
],
|
|
51
57
|
rehypePlugins: [
|
|
52
|
-
[rehypeShiki, { themes: { light: "github-light", dark: "github-dark" } }]
|
|
58
|
+
[rehypeShiki, { themes: { light: "github-light", dark: "github-dark" }, defaultColor: false }]
|
|
53
59
|
],
|
|
54
60
|
mdExtensions: [".md"],
|
|
55
61
|
mdxExtensions: [".mdx"]
|
|
@@ -215,18 +221,124 @@ var init_dev = __esm(() => {
|
|
|
215
221
|
init_vite_config();
|
|
216
222
|
});
|
|
217
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, "package.json"), JSON.stringify({ type: "module" }, null, 2));
|
|
255
|
+
await fs3.writeFile(path7.resolve(funcDir, ".vc-config.json"), JSON.stringify({
|
|
256
|
+
runtime: "nodejs24.x",
|
|
257
|
+
handler: "entry-vercel.js",
|
|
258
|
+
launcherType: "Nodejs"
|
|
259
|
+
}, null, 2));
|
|
260
|
+
await fs3.writeFile(path7.resolve(outputDir, "config.json"), JSON.stringify({
|
|
261
|
+
version: 3,
|
|
262
|
+
routes: [
|
|
263
|
+
{ handle: "filesystem" },
|
|
264
|
+
{ src: "/(.*)", dest: "/index" }
|
|
265
|
+
]
|
|
266
|
+
}, null, 2));
|
|
267
|
+
console.log(chalk5.green("Vercel output generated →"), outputDir);
|
|
268
|
+
}
|
|
269
|
+
async function copyDir(src, dest) {
|
|
270
|
+
await fs3.mkdir(dest, { recursive: true });
|
|
271
|
+
const entries = await fs3.readdir(src, { withFileTypes: true });
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
const srcPath = path7.join(src, entry.name);
|
|
274
|
+
const destPath = path7.join(dest, entry.name);
|
|
275
|
+
if (entry.isDirectory()) {
|
|
276
|
+
await copyDir(srcPath, destPath);
|
|
277
|
+
} else {
|
|
278
|
+
await fs3.copyFile(srcPath, destPath);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function copyContentAssets(contentDir, staticDir) {
|
|
283
|
+
const entries = await fs3.readdir(contentDir, { withFileTypes: true });
|
|
284
|
+
for (const entry of entries) {
|
|
285
|
+
const srcPath = path7.join(contentDir, entry.name);
|
|
286
|
+
if (entry.isDirectory()) {
|
|
287
|
+
const destSubDir = path7.join(staticDir, entry.name);
|
|
288
|
+
await copyContentAssetsRecursive(srcPath, destSubDir);
|
|
289
|
+
} else {
|
|
290
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
291
|
+
if (CONTENT_EXTENSIONS.has(ext)) {
|
|
292
|
+
await fs3.copyFile(srcPath, path7.join(staticDir, entry.name));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function copyContentAssetsRecursive(srcDir, destDir) {
|
|
298
|
+
const entries = await fs3.readdir(srcDir, { withFileTypes: true });
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
const srcPath = path7.join(srcDir, entry.name);
|
|
301
|
+
if (entry.isDirectory()) {
|
|
302
|
+
await copyContentAssetsRecursive(srcPath, path7.join(destDir, entry.name));
|
|
303
|
+
} else {
|
|
304
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
305
|
+
if (CONTENT_EXTENSIONS.has(ext)) {
|
|
306
|
+
await fs3.mkdir(destDir, { recursive: true });
|
|
307
|
+
await fs3.copyFile(srcPath, path7.join(destDir, entry.name));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
var CONTENT_EXTENSIONS;
|
|
313
|
+
var init_vercel = __esm(() => {
|
|
314
|
+
CONTENT_EXTENSIONS = new Set([
|
|
315
|
+
".png",
|
|
316
|
+
".jpg",
|
|
317
|
+
".jpeg",
|
|
318
|
+
".gif",
|
|
319
|
+
".svg",
|
|
320
|
+
".webp",
|
|
321
|
+
".ico",
|
|
322
|
+
".pdf",
|
|
323
|
+
".json",
|
|
324
|
+
".yaml",
|
|
325
|
+
".yml",
|
|
326
|
+
".txt"
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
218
330
|
// src/server/prod.ts
|
|
219
331
|
var exports_prod = {};
|
|
220
332
|
__export(exports_prod, {
|
|
221
333
|
startProdServer: () => startProdServer
|
|
222
334
|
});
|
|
223
|
-
import
|
|
224
|
-
import
|
|
335
|
+
import path9 from "path";
|
|
336
|
+
import chalk7 from "chalk";
|
|
225
337
|
async function startProdServer(options) {
|
|
226
338
|
const { port, distDir } = options;
|
|
227
|
-
const serverEntry =
|
|
339
|
+
const serverEntry = path9.resolve(distDir, "server/entry-prod.js");
|
|
228
340
|
const { startServer } = await import(serverEntry);
|
|
229
|
-
console.log(
|
|
341
|
+
console.log(chalk7.cyan("Starting production server..."));
|
|
230
342
|
return startServer({ port, distDir });
|
|
231
343
|
}
|
|
232
344
|
var init_prod = () => {};
|
|
@@ -436,78 +548,87 @@ var devCommand = new Command2("dev").description("Start development server").opt
|
|
|
436
548
|
|
|
437
549
|
// src/cli/commands/build.ts
|
|
438
550
|
import { Command as Command3 } from "commander";
|
|
439
|
-
import
|
|
440
|
-
import
|
|
441
|
-
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) => {
|
|
551
|
+
import path8 from "path";
|
|
552
|
+
import chalk6 from "chalk";
|
|
553
|
+
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) => {
|
|
442
554
|
const contentDir = resolveContentDir(options.content);
|
|
443
|
-
const outDir =
|
|
555
|
+
const outDir = path8.resolve(options.outDir);
|
|
444
556
|
process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
|
|
445
557
|
process.env.CHRONICLE_CONTENT_DIR = contentDir;
|
|
446
|
-
console.log(
|
|
558
|
+
console.log(chalk6.cyan("Building for production..."));
|
|
447
559
|
const { build } = await import("vite");
|
|
448
560
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
449
561
|
const baseConfig = await createViteConfig2({ root: PACKAGE_ROOT, contentDir });
|
|
450
|
-
console.log(
|
|
562
|
+
console.log(chalk6.gray("Building client..."));
|
|
451
563
|
await build({
|
|
452
564
|
...baseConfig,
|
|
453
565
|
build: {
|
|
454
|
-
outDir:
|
|
566
|
+
outDir: path8.join(outDir, "client"),
|
|
455
567
|
ssrManifest: true,
|
|
456
568
|
rolldownOptions: {
|
|
457
|
-
input:
|
|
569
|
+
input: path8.resolve(PACKAGE_ROOT, "src/server/index.html")
|
|
458
570
|
}
|
|
459
571
|
}
|
|
460
572
|
});
|
|
461
|
-
|
|
573
|
+
const serverEntry = options.adapter === "vercel" ? path8.resolve(PACKAGE_ROOT, "src/server/entry-vercel.ts") : path8.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts");
|
|
574
|
+
console.log(chalk6.gray("Building server..."));
|
|
462
575
|
await build({
|
|
463
576
|
...baseConfig,
|
|
464
577
|
ssr: {
|
|
465
578
|
noExternal: true
|
|
466
579
|
},
|
|
467
580
|
build: {
|
|
468
|
-
outDir:
|
|
469
|
-
ssr:
|
|
581
|
+
outDir: path8.join(outDir, "server"),
|
|
582
|
+
ssr: serverEntry
|
|
470
583
|
}
|
|
471
584
|
});
|
|
472
|
-
console.log(
|
|
585
|
+
console.log(chalk6.green("Build complete →"), outDir);
|
|
586
|
+
if (options.adapter === "vercel") {
|
|
587
|
+
const { buildVercelOutput: buildVercelOutput2 } = await Promise.resolve().then(() => (init_vercel(), exports_vercel));
|
|
588
|
+
await buildVercelOutput2({
|
|
589
|
+
distDir: outDir,
|
|
590
|
+
contentDir,
|
|
591
|
+
projectRoot: process.cwd()
|
|
592
|
+
});
|
|
593
|
+
}
|
|
473
594
|
});
|
|
474
595
|
|
|
475
596
|
// src/cli/commands/start.ts
|
|
476
597
|
import { Command as Command4 } from "commander";
|
|
477
|
-
import
|
|
478
|
-
import
|
|
598
|
+
import path10 from "path";
|
|
599
|
+
import chalk8 from "chalk";
|
|
479
600
|
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) => {
|
|
480
601
|
const contentDir = resolveContentDir(options.content);
|
|
481
602
|
const port = parseInt(options.port, 10);
|
|
482
|
-
const distDir =
|
|
603
|
+
const distDir = path10.resolve(options.dist);
|
|
483
604
|
process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
|
|
484
605
|
process.env.CHRONICLE_CONTENT_DIR = contentDir;
|
|
485
|
-
console.log(
|
|
606
|
+
console.log(chalk8.cyan("Starting production server..."));
|
|
486
607
|
const { startProdServer: startProdServer2 } = await Promise.resolve().then(() => (init_prod(), exports_prod));
|
|
487
608
|
await startProdServer2({ port, root: PACKAGE_ROOT, distDir });
|
|
488
609
|
});
|
|
489
610
|
|
|
490
611
|
// src/cli/commands/serve.ts
|
|
491
612
|
import { Command as Command5 } from "commander";
|
|
492
|
-
import
|
|
493
|
-
import
|
|
613
|
+
import path11 from "path";
|
|
614
|
+
import chalk9 from "chalk";
|
|
494
615
|
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) => {
|
|
495
616
|
const contentDir = resolveContentDir(options.content);
|
|
496
617
|
const port = parseInt(options.port, 10);
|
|
497
|
-
const outDir =
|
|
618
|
+
const outDir = path11.resolve(options.outDir);
|
|
498
619
|
process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
|
|
499
620
|
process.env.CHRONICLE_CONTENT_DIR = contentDir;
|
|
500
|
-
console.log(
|
|
621
|
+
console.log(chalk9.cyan("Building for production..."));
|
|
501
622
|
const { build } = await import("vite");
|
|
502
623
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
503
624
|
const baseConfig = await createViteConfig2({ root: PACKAGE_ROOT, contentDir });
|
|
504
625
|
await build({
|
|
505
626
|
...baseConfig,
|
|
506
627
|
build: {
|
|
507
|
-
outDir:
|
|
628
|
+
outDir: path11.join(outDir, "client"),
|
|
508
629
|
ssrManifest: true,
|
|
509
630
|
rolldownOptions: {
|
|
510
|
-
input:
|
|
631
|
+
input: path11.resolve(PACKAGE_ROOT, "src/server/index.html")
|
|
511
632
|
}
|
|
512
633
|
}
|
|
513
634
|
});
|
|
@@ -517,11 +638,11 @@ var serveCommand = new Command5("serve").description("Build and start production
|
|
|
517
638
|
noExternal: true
|
|
518
639
|
},
|
|
519
640
|
build: {
|
|
520
|
-
outDir:
|
|
521
|
-
ssr:
|
|
641
|
+
outDir: path11.join(outDir, "server"),
|
|
642
|
+
ssr: path11.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts")
|
|
522
643
|
}
|
|
523
644
|
});
|
|
524
|
-
console.log(
|
|
645
|
+
console.log(chalk9.cyan("Starting production server..."));
|
|
525
646
|
const { startProdServer: startProdServer2 } = await Promise.resolve().then(() => (init_prod(), exports_prod));
|
|
526
647
|
await startProdServer2({ port, root: PACKAGE_ROOT, distDir: outDir });
|
|
527
648
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raystack/chronicle",
|
|
3
|
-
"version": "0.1.0-canary.
|
|
3
|
+
"version": "0.1.0-canary.729744c",
|
|
4
4
|
"description": "Config-driven documentation framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"mermaid": "^11.13.0",
|
|
49
49
|
"openapi-types": "^12.1.3",
|
|
50
50
|
"react": "^19.0.0",
|
|
51
|
-
"react-device-detect": "^2.2.3",
|
|
52
51
|
"react-dom": "^19.0.0",
|
|
53
52
|
"react-router-dom": "^7.13.1",
|
|
54
53
|
"remark-directive": "^4.0.0",
|
|
@@ -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
|
|
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:
|
|
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
|
})
|
|
@@ -5,7 +5,6 @@ import { useNavigate } from "react-router-dom";
|
|
|
5
5
|
import { Button, Command, Dialog, Text } from "@raystack/apsara";
|
|
6
6
|
import { cx } from "class-variance-authority";
|
|
7
7
|
import { DocumentIcon, HashtagIcon } from "@heroicons/react/24/outline";
|
|
8
|
-
import { isMacOs } from "react-device-detect";
|
|
9
8
|
import { MethodBadge } from "@/components/api/method-badge";
|
|
10
9
|
import styles from "./search.module.css";
|
|
11
10
|
|
|
@@ -45,7 +44,7 @@ function SearchShortcutKey({ className }: { className?: string }) {
|
|
|
45
44
|
const [key, setKey] = useState("\u2318");
|
|
46
45
|
|
|
47
46
|
useEffect(() => {
|
|
48
|
-
setKey(
|
|
47
|
+
setKey(navigator.platform?.startsWith("Mac") ? "\u2318" : "Ctrl");
|
|
49
48
|
}, []);
|
|
50
49
|
|
|
51
50
|
return (
|
|
@@ -0,0 +1,133 @@
|
|
|
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 package.json for ESM support
|
|
53
|
+
await fs.writeFile(
|
|
54
|
+
path.resolve(funcDir, 'package.json'),
|
|
55
|
+
JSON.stringify({ type: 'module' }, null, 2),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// 6. Write .vc-config.json
|
|
59
|
+
await fs.writeFile(
|
|
60
|
+
path.resolve(funcDir, '.vc-config.json'),
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
runtime: 'nodejs24.x',
|
|
63
|
+
handler: 'entry-vercel.js',
|
|
64
|
+
launcherType: 'Nodejs',
|
|
65
|
+
}, null, 2),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// 7. Write config.json
|
|
69
|
+
await fs.writeFile(
|
|
70
|
+
path.resolve(outputDir, 'config.json'),
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
version: 3,
|
|
73
|
+
routes: [
|
|
74
|
+
{ handle: 'filesystem' },
|
|
75
|
+
{ src: '/(.*)', dest: '/index' },
|
|
76
|
+
],
|
|
77
|
+
}, null, 2),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
console.log(chalk.green('Vercel output generated →'), outputDir)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function copyDir(src: string, dest: string) {
|
|
84
|
+
await fs.mkdir(dest, { recursive: true })
|
|
85
|
+
const entries = await fs.readdir(src, { withFileTypes: true })
|
|
86
|
+
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const srcPath = path.join(src, entry.name)
|
|
89
|
+
const destPath = path.join(dest, entry.name)
|
|
90
|
+
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
await copyDir(srcPath, destPath)
|
|
93
|
+
} else {
|
|
94
|
+
await fs.copyFile(srcPath, destPath)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function copyContentAssets(contentDir: string, staticDir: string) {
|
|
100
|
+
const entries = await fs.readdir(contentDir, { withFileTypes: true })
|
|
101
|
+
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const srcPath = path.join(contentDir, entry.name)
|
|
104
|
+
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
const destSubDir = path.join(staticDir, entry.name)
|
|
107
|
+
await copyContentAssetsRecursive(srcPath, destSubDir)
|
|
108
|
+
} else {
|
|
109
|
+
const ext = path.extname(entry.name).toLowerCase()
|
|
110
|
+
if (CONTENT_EXTENSIONS.has(ext)) {
|
|
111
|
+
await fs.copyFile(srcPath, path.join(staticDir, entry.name))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function copyContentAssetsRecursive(srcDir: string, destDir: string) {
|
|
118
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true })
|
|
119
|
+
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
const srcPath = path.join(srcDir, entry.name)
|
|
122
|
+
|
|
123
|
+
if (entry.isDirectory()) {
|
|
124
|
+
await copyContentAssetsRecursive(srcPath, path.join(destDir, entry.name))
|
|
125
|
+
} else {
|
|
126
|
+
const ext = path.extname(entry.name).toLowerCase()
|
|
127
|
+
if (CONTENT_EXTENSIONS.has(ext)) {
|
|
128
|
+
await fs.mkdir(destDir, { recursive: true })
|
|
129
|
+
await fs.copyFile(srcPath, path.join(destDir, entry.name))
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/server/entry-prod.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
38
|
+
// API routes — handled by shared request handler
|
|
39
|
+
const routeHandler = matchRoute(new URL(url, baseUrl).href)
|
|
32
40
|
if (routeHandler) {
|
|
33
|
-
const
|
|
34
|
-
|
|
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
|
-
//
|
|
71
|
-
const
|
|
72
|
-
|
|
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
|
+
}
|
|
@@ -25,6 +25,12 @@ export async function createViteConfig(options: ViteConfigOptions): Promise<Inli
|
|
|
25
25
|
'@': path.resolve(root, 'src'),
|
|
26
26
|
'@content': contentDir,
|
|
27
27
|
},
|
|
28
|
+
dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],
|
|
29
|
+
},
|
|
30
|
+
server: {
|
|
31
|
+
fs: {
|
|
32
|
+
allow: [root, contentDir],
|
|
33
|
+
},
|
|
28
34
|
},
|
|
29
35
|
plugins: [
|
|
30
36
|
mdx({
|
|
@@ -35,7 +41,7 @@ export async function createViteConfig(options: ViteConfigOptions): Promise<Inli
|
|
|
35
41
|
remarkDirective,
|
|
36
42
|
],
|
|
37
43
|
rehypePlugins: [
|
|
38
|
-
[rehypeShiki, { themes: { light: 'github-light', dark: 'github-dark' } }],
|
|
44
|
+
[rehypeShiki, { themes: { light: 'github-light', dark: 'github-dark' }, defaultColor: false }],
|
|
39
45
|
],
|
|
40
46
|
mdExtensions: ['.md'],
|
|
41
47
|
mdxExtensions: ['.mdx'],
|
|
@@ -38,18 +38,22 @@
|
|
|
38
38
|
margin-bottom: var(--rs-space-3);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
.content :global(pre
|
|
42
|
-
color: var(--shiki-light);
|
|
41
|
+
.content :global(pre) {
|
|
42
|
+
background-color: var(--shiki-light-bg, #fff);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
color: var(--shiki-
|
|
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;
|