85 lines
2.6 KiB
JavaScript
85 lines
2.6 KiB
JavaScript
import http from 'node:http'
|
|||
|
|
import fs from 'node:fs'
|
||
|
|
import path from 'node:path'
|
||
|
|
import { fileURLToPath } from 'node:url'
|
||
|
|
|
||
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||
|
|
const port = Number(process.env.PORT || 7676)
|
||
|
|
const distDir = path.resolve(process.env.DIST_DIR || path.join(__dirname, '..', 'dist'))
|
||
|
|
const rawBase = String(process.env.VITE_BASE_PATH ?? '/admin/').trim() || '/admin/'
|
||
|
|
const publicBase = rawBase === '/' ? '' : rawBase.replace(/\/+$/, '')
|
||
|
|
|
||
|
|
const MIME = {
|
||
|
|
'.html': 'text/html; charset=utf-8',
|
||
|
|
'.js': 'text/javascript; charset=utf-8',
|
||
|
|
'.mjs': 'text/javascript; charset=utf-8',
|
||
|
|
'.css': 'text/css; charset=utf-8',
|
||
|
|
'.json': 'application/json; charset=utf-8',
|
||
|
|
'.map': 'application/json; charset=utf-8',
|
||
|
|
'.svg': 'image/svg+xml',
|
||
|
|
'.png': 'image/png',
|
||
|
|
'.jpg': 'image/jpeg',
|
||
|
|
'.jpeg': 'image/jpeg',
|
||
|
|
'.gif': 'image/gif',
|
||
|
|
'.webp': 'image/webp',
|
||
|
|
'.ico': 'image/x-icon',
|
||
|
|
'.woff': 'font/woff',
|
||
|
|
'.woff2': 'font/woff2',
|
||
|
|
'.ttf': 'font/ttf',
|
||
|
|
'.txt': 'text/plain; charset=utf-8',
|
||
|
|
'.webmanifest': 'application/manifest+json',
|
||
|
|
}
|
||
|
|
|
||
|
|
function sendFile(res, filePath) {
|
||
|
|
const type = MIME[path.extname(filePath).toLowerCase()] || 'application/octet-stream'
|
||
|
|
res.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'public, max-age=0' })
|
||
|
|
fs.createReadStream(filePath).pipe(res)
|
||
|
|
}
|
||
|
|
|
||
|
|
function sendIndex(res) {
|
||
|
|
sendFile(res, path.join(distDir, 'index.html'))
|
||
|
|
}
|
||
|
|
|
||
|
|
function resolveSafe(urlPath) {
|
||
|
|
const decoded = decodeURIComponent(urlPath.split('?')[0] || '/')
|
||
|
|
const relative = publicBase && decoded.startsWith(`${publicBase}/`)
|
||
|
|
? decoded.slice(publicBase.length)
|
||
|
|
: publicBase && decoded === publicBase
|
||
|
|
? '/'
|
||
|
|
: decoded
|
||
|
|
const target = path.resolve(distDir, `.${relative}`)
|
||
|
|
if (!target.startsWith(distDir)) return null
|
||
|
|
return target
|
||
|
|
}
|
||
|
|
|
||
|
|
const server = http.createServer((req, res) => {
|
||
|
|
const url = req.url || '/'
|
||
|
|
const pathname = decodeURIComponent(url.split('?')[0] || '/')
|
||
|
|
|
||
|
|
if (pathname === '/health' || (publicBase && pathname === `${publicBase}/health`)) {
|
||
|
|
res.writeHead(200, { 'Content-Type': 'text/plain' })
|
||
|
|
return res.end('ok')
|
||
|
|
}
|
||
|
|
|
||
|
|
if (publicBase && pathname === publicBase) {
|
||
|
|
res.writeHead(302, { Location: `${publicBase}/` })
|
||
|
|
return res.end()
|
||
|
|
}
|
||
|
|
|
||
|
|
if (publicBase && pathname === '/') {
|
||
|
|
res.writeHead(302, { Location: `${publicBase}/` })
|
||
|
|
return res.end()
|
||
|
|
}
|
||
|
|
|
||
|
|
const target = resolveSafe(url)
|
||
|
|
if (target && fs.existsSync(target) && fs.statSync(target).isFile()) {
|
||
|
|
return sendFile(res, target)
|
||
|
|
}
|
||
|
|
|
||
|
|
return sendIndex(res)
|
||
|
|
})
|
||
|
|
|
||
|
|
server.listen(port, '0.0.0.0', () => {
|
||
|
|
console.log(`fabric-trade admin listening on ${port} → ${publicBase || ''}/`)
|
||
|
|
})
|