@raystack/chronicle 0.1.0-canary.9b7d924 → 0.1.0-canary.a320792
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 +29 -145
- package/package.json +1 -1
- package/src/cli/commands/build.ts +2 -18
- package/src/server/entry-prod.ts +47 -17
- package/src/server/adapters/vercel.ts +0 -133
- package/src/server/entry-vercel.ts +0 -28
- package/src/server/request-handler.ts +0 -63
package/dist/cli/index.js
CHANGED
|
@@ -221,124 +221,18 @@ 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, "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
|
-
|
|
330
224
|
// src/server/prod.ts
|
|
331
225
|
var exports_prod = {};
|
|
332
226
|
__export(exports_prod, {
|
|
333
227
|
startProdServer: () => startProdServer
|
|
334
228
|
});
|
|
335
|
-
import
|
|
336
|
-
import
|
|
229
|
+
import path8 from "path";
|
|
230
|
+
import chalk6 from "chalk";
|
|
337
231
|
async function startProdServer(options) {
|
|
338
232
|
const { port, distDir } = options;
|
|
339
|
-
const serverEntry =
|
|
233
|
+
const serverEntry = path8.resolve(distDir, "server/entry-prod.js");
|
|
340
234
|
const { startServer } = await import(serverEntry);
|
|
341
|
-
console.log(
|
|
235
|
+
console.log(chalk6.cyan("Starting production server..."));
|
|
342
236
|
return startServer({ port, distDir });
|
|
343
237
|
}
|
|
344
238
|
var init_prod = () => {};
|
|
@@ -548,88 +442,78 @@ var devCommand = new Command2("dev").description("Start development server").opt
|
|
|
548
442
|
|
|
549
443
|
// src/cli/commands/build.ts
|
|
550
444
|
import { Command as Command3 } from "commander";
|
|
551
|
-
import
|
|
552
|
-
import
|
|
553
|
-
var buildCommand = new Command3("build").description("Build for production").option("-c, --content <path>", "Content directory").option("-o, --outDir <path>", "Output directory", "dist").
|
|
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) => {
|
|
554
448
|
const contentDir = resolveContentDir(options.content);
|
|
555
|
-
const outDir =
|
|
449
|
+
const outDir = path7.resolve(options.outDir);
|
|
556
450
|
process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
|
|
557
451
|
process.env.CHRONICLE_CONTENT_DIR = contentDir;
|
|
558
|
-
console.log(
|
|
452
|
+
console.log(chalk5.cyan("Building for production..."));
|
|
559
453
|
const { build } = await import("vite");
|
|
560
454
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
561
455
|
const baseConfig = await createViteConfig2({ root: PACKAGE_ROOT, contentDir });
|
|
562
|
-
console.log(
|
|
456
|
+
console.log(chalk5.gray("Building client..."));
|
|
563
457
|
await build({
|
|
564
458
|
...baseConfig,
|
|
565
459
|
build: {
|
|
566
|
-
outDir:
|
|
460
|
+
outDir: path7.join(outDir, "client"),
|
|
567
461
|
ssrManifest: true,
|
|
568
462
|
rolldownOptions: {
|
|
569
|
-
input:
|
|
463
|
+
input: path7.resolve(PACKAGE_ROOT, "src/server/index.html")
|
|
570
464
|
}
|
|
571
465
|
}
|
|
572
466
|
});
|
|
573
|
-
|
|
574
|
-
console.log(chalk6.gray("Building server..."));
|
|
467
|
+
console.log(chalk5.gray("Building server..."));
|
|
575
468
|
await build({
|
|
576
469
|
...baseConfig,
|
|
577
470
|
ssr: {
|
|
578
471
|
noExternal: true
|
|
579
472
|
},
|
|
580
473
|
build: {
|
|
581
|
-
outDir:
|
|
582
|
-
ssr:
|
|
583
|
-
target: "node22"
|
|
474
|
+
outDir: path7.join(outDir, "server"),
|
|
475
|
+
ssr: path7.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts")
|
|
584
476
|
}
|
|
585
477
|
});
|
|
586
|
-
console.log(
|
|
587
|
-
if (options.adapter === "vercel") {
|
|
588
|
-
const { buildVercelOutput: buildVercelOutput2 } = await Promise.resolve().then(() => (init_vercel(), exports_vercel));
|
|
589
|
-
await buildVercelOutput2({
|
|
590
|
-
distDir: outDir,
|
|
591
|
-
contentDir,
|
|
592
|
-
projectRoot: process.cwd()
|
|
593
|
-
});
|
|
594
|
-
}
|
|
478
|
+
console.log(chalk5.green("Build complete →"), outDir);
|
|
595
479
|
});
|
|
596
480
|
|
|
597
481
|
// src/cli/commands/start.ts
|
|
598
482
|
import { Command as Command4 } from "commander";
|
|
599
|
-
import
|
|
600
|
-
import
|
|
483
|
+
import path9 from "path";
|
|
484
|
+
import chalk7 from "chalk";
|
|
601
485
|
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) => {
|
|
602
486
|
const contentDir = resolveContentDir(options.content);
|
|
603
487
|
const port = parseInt(options.port, 10);
|
|
604
|
-
const distDir =
|
|
488
|
+
const distDir = path9.resolve(options.dist);
|
|
605
489
|
process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
|
|
606
490
|
process.env.CHRONICLE_CONTENT_DIR = contentDir;
|
|
607
|
-
console.log(
|
|
491
|
+
console.log(chalk7.cyan("Starting production server..."));
|
|
608
492
|
const { startProdServer: startProdServer2 } = await Promise.resolve().then(() => (init_prod(), exports_prod));
|
|
609
493
|
await startProdServer2({ port, root: PACKAGE_ROOT, distDir });
|
|
610
494
|
});
|
|
611
495
|
|
|
612
496
|
// src/cli/commands/serve.ts
|
|
613
497
|
import { Command as Command5 } from "commander";
|
|
614
|
-
import
|
|
615
|
-
import
|
|
498
|
+
import path10 from "path";
|
|
499
|
+
import chalk8 from "chalk";
|
|
616
500
|
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) => {
|
|
617
501
|
const contentDir = resolveContentDir(options.content);
|
|
618
502
|
const port = parseInt(options.port, 10);
|
|
619
|
-
const outDir =
|
|
503
|
+
const outDir = path10.resolve(options.outDir);
|
|
620
504
|
process.env.CHRONICLE_PROJECT_ROOT = process.cwd();
|
|
621
505
|
process.env.CHRONICLE_CONTENT_DIR = contentDir;
|
|
622
|
-
console.log(
|
|
506
|
+
console.log(chalk8.cyan("Building for production..."));
|
|
623
507
|
const { build } = await import("vite");
|
|
624
508
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
625
509
|
const baseConfig = await createViteConfig2({ root: PACKAGE_ROOT, contentDir });
|
|
626
510
|
await build({
|
|
627
511
|
...baseConfig,
|
|
628
512
|
build: {
|
|
629
|
-
outDir:
|
|
513
|
+
outDir: path10.join(outDir, "client"),
|
|
630
514
|
ssrManifest: true,
|
|
631
515
|
rolldownOptions: {
|
|
632
|
-
input:
|
|
516
|
+
input: path10.resolve(PACKAGE_ROOT, "src/server/index.html")
|
|
633
517
|
}
|
|
634
518
|
}
|
|
635
519
|
});
|
|
@@ -639,11 +523,11 @@ var serveCommand = new Command5("serve").description("Build and start production
|
|
|
639
523
|
noExternal: true
|
|
640
524
|
},
|
|
641
525
|
build: {
|
|
642
|
-
outDir:
|
|
643
|
-
ssr:
|
|
526
|
+
outDir: path10.join(outDir, "server"),
|
|
527
|
+
ssr: path10.resolve(PACKAGE_ROOT, "src/server/entry-prod.ts")
|
|
644
528
|
}
|
|
645
529
|
});
|
|
646
|
-
console.log(
|
|
530
|
+
console.log(chalk8.cyan("Starting production server..."));
|
|
647
531
|
const { startProdServer: startProdServer2 } = await Promise.resolve().then(() => (init_prod(), exports_prod));
|
|
648
532
|
await startProdServer2({ port, root: PACKAGE_ROOT, distDir: outDir });
|
|
649
533
|
});
|
package/package.json
CHANGED
|
@@ -8,7 +8,6 @@ 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)')
|
|
12
11
|
.action(async (options) => {
|
|
13
12
|
const contentDir = resolveContentDir(options.content)
|
|
14
13
|
const outDir = path.resolve(options.outDir)
|
|
@@ -36,11 +35,7 @@ export const buildCommand = new Command('build')
|
|
|
36
35
|
},
|
|
37
36
|
})
|
|
38
37
|
|
|
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
|
-
|
|
38
|
+
// Build server bundle (noExternal: true to bundle all deps for portability)
|
|
44
39
|
console.log(chalk.gray('Building server...'))
|
|
45
40
|
await build({
|
|
46
41
|
...baseConfig,
|
|
@@ -49,20 +44,9 @@ export const buildCommand = new Command('build')
|
|
|
49
44
|
},
|
|
50
45
|
build: {
|
|
51
46
|
outDir: path.join(outDir, 'server'),
|
|
52
|
-
ssr:
|
|
53
|
-
target: 'node22',
|
|
47
|
+
ssr: path.resolve(PACKAGE_ROOT, 'src/server/entry-prod.ts'),
|
|
54
48
|
},
|
|
55
49
|
})
|
|
56
50
|
|
|
57
51
|
console.log(chalk.green('Build complete →'), outDir)
|
|
58
|
-
|
|
59
|
-
// Run Vercel adapter post-build
|
|
60
|
-
if (options.adapter === 'vercel') {
|
|
61
|
-
const { buildVercelOutput } = await import('@/server/adapters/vercel')
|
|
62
|
-
await buildVercelOutput({
|
|
63
|
-
distDir: outDir,
|
|
64
|
-
contentDir,
|
|
65
|
-
projectRoot: process.cwd(),
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
52
|
})
|
package/src/server/entry-prod.ts
CHANGED
|
@@ -3,22 +3,16 @@ 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'
|
|
6
7
|
import { render } from './entry-server'
|
|
7
8
|
import { matchRoute } from './router'
|
|
8
9
|
import { loadConfig } from '@/lib/config'
|
|
9
10
|
import { loadApiSpecs } from '@/lib/openapi'
|
|
10
11
|
import { getPage, loadPageComponent, buildPageTree } from '@/lib/source'
|
|
11
|
-
import {
|
|
12
|
+
import { mdxComponents } from '@/components/mdx'
|
|
12
13
|
|
|
13
14
|
export { render, matchRoute, loadConfig, loadApiSpecs, getPage, loadPageComponent, buildPageTree }
|
|
14
15
|
|
|
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
16
|
export async function startServer(options: { port: number; distDir: string }) {
|
|
23
17
|
const { port, distDir } = options
|
|
24
18
|
|
|
@@ -29,17 +23,19 @@ export async function startServer(options: { port: number; distDir: string }) {
|
|
|
29
23
|
const sirv = (await import('sirv')).default
|
|
30
24
|
const assets = sirv(clientDir, { gzip: true })
|
|
31
25
|
|
|
32
|
-
const baseUrl = `http://localhost:${port}`
|
|
33
|
-
|
|
34
26
|
const server = createServer(async (req, res) => {
|
|
35
27
|
const url = req.url || '/'
|
|
36
28
|
|
|
37
29
|
try {
|
|
38
|
-
// API routes
|
|
39
|
-
const routeHandler = matchRoute(new URL(url,
|
|
30
|
+
// API routes
|
|
31
|
+
const routeHandler = matchRoute(new URL(url, `http://localhost:${port}`).href)
|
|
40
32
|
if (routeHandler) {
|
|
41
|
-
const
|
|
42
|
-
await
|
|
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)
|
|
43
39
|
return
|
|
44
40
|
}
|
|
45
41
|
|
|
@@ -71,9 +67,43 @@ export async function startServer(options: { port: number; distDir: string }) {
|
|
|
71
67
|
})
|
|
72
68
|
if (assetHandled) return
|
|
73
69
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
|
|
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)
|
|
77
107
|
} catch (e) {
|
|
78
108
|
console.error(e)
|
|
79
109
|
res.statusCode = 500
|
|
@@ -1,133 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,28 +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 { fileURLToPath } from 'url'
|
|
5
|
-
import path from 'path'
|
|
6
|
-
import { handleRequest } from './request-handler'
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
const templatePath = path.resolve(__dirname, 'index.html')
|
|
10
|
-
const template = readFileSync(templatePath, 'utf-8')
|
|
11
|
-
|
|
12
|
-
export default async function handler(req: IncomingMessage, res: ServerResponse) {
|
|
13
|
-
const url = req.url || '/'
|
|
14
|
-
const baseUrl = `https://${req.headers.host || 'localhost'}`
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const response = await handleRequest(url, { template, baseUrl })
|
|
18
|
-
|
|
19
|
-
res.statusCode = response.status
|
|
20
|
-
response.headers.forEach((value: string, key: string) => res.setHeader(key, value))
|
|
21
|
-
const body = await response.text()
|
|
22
|
-
res.end(body)
|
|
23
|
-
} catch (e) {
|
|
24
|
-
console.error(e)
|
|
25
|
-
res.statusCode = 500
|
|
26
|
-
res.end((e as Error).message)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -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
|
-
}
|