返回 slidev
basePathServer.ts
根目录 / cypress / basePathServer.ts
1 import type { Server } from 'node:http'
2 import type { AddressInfo } from 'node:net'
3 import { execFileSync } from 'node:child_process'
4 import { createReadStream } from 'node:fs'
5 import { stat } from 'node:fs/promises'
6 import { createServer } from 'node:http'
7 import { extname, join, normalize, sep } from 'node:path'
8 import process from 'node:process'
9
10 let server: Server | undefined
11 let baseUrl: string | undefined
12
13 const root = join(import.meta.dirname, 'fixtures/basic/dist')
14 const base = '/deck/'
15 const contentTypes = new Map([
16 ['.html', 'text/html; charset=utf-8'],
17 ['.js', 'text/javascript; charset=utf-8'],
18 ['.css', 'text/css; charset=utf-8'],
19 ['.png', 'image/png'],
20 ['.svg', 'image/svg+xml'],
21 ['.woff', 'font/woff'],
22 ['.woff2', 'font/woff2'],
23 ['.ttf', 'font/ttf'],
24 ['.json', 'application/json'],
25 ])
26
27 async function fileExists(file: string) {
28 try {
29 const info = await stat(file)
30 return info.isFile()
31 }
32 catch {
33 return false
34 }
35 }
36
37 /**
38 * Builds the basic fixture with a non-root `--base` and serves the static
39 * output (with SPA fallback) on an ephemeral port.
40 *
41 * Returns the served base URL, e.g. `http://127.0.0.1:52341/deck/`.
42 */
43 export async function startBasePathServer(): Promise<string> {
44 if (server && baseUrl)
45 return baseUrl
46
47 // Cypress runs its node events inside Electron; strip its env so the
48 // spawned build runs under plain Node.
49 const pnpm = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
50 const env = { ...process.env }
51 delete env.ELECTRON_RUN_AS_NODE
52 delete env.NODE_OPTIONS
53
54 execFileSync(pnpm, ['--filter', './cypress/fixtures/basic', 'build', '--base', base], {
55 cwd: join(import.meta.dirname, '..'),
56 env,
57 stdio: 'inherit',
58 })
59
60 server = createServer(async (req, res) => {
61 const url = new URL(req.url ?? '/', 'http://127.0.0.1')
62
63 if (!url.pathname.startsWith(base)) {
64 res.writeHead(404).end('Not found')
65 return
66 }
67
68 const rel = decodeURIComponent(url.pathname.slice(base.length)) || 'index.html'
69 let file = normalize(join(root, rel))
70 if (file !== root && !file.startsWith(root + sep)) {
71 res.writeHead(403).end('Forbidden')
72 return
73 }
74
75 if (!await fileExists(file))
76 file = join(root, 'index.html')
77
78 res.writeHead(200, { 'content-type': contentTypes.get(extname(file)) ?? 'application/octet-stream' })
79 createReadStream(file).pipe(res)
80 })
81
82 await new Promise<void>((resolve, reject) => {
83 server!.once('error', reject)
84 server!.listen(0, '127.0.0.1', resolve)
85 })
86
87 const { port } = server.address() as AddressInfo
88 baseUrl = `http://127.0.0.1:${port}${base}`
89 return baseUrl
90 }
91
92 export function stopBasePathServer() {
93 return new Promise<null>((resolve, reject) => {
94 if (!server) {
95 resolve(null)
96 return
97 }
98
99 server.close((error) => {
100 server = undefined
101 baseUrl = undefined
102 if (error)
103 reject(error)
104 else
105 resolve(null)
106 })
107 })
108 }
109
109 lines TYPESCRIPT