skillhub/web/vite.config.ts
XiaoSeS d824a0498c
Some checks are pending
Deploy Docs / build (push) Waiting to run
Deploy Docs / Deploy (push) Blocked by required conditions
Security / Dependency Review (push) Waiting to run
Security / CodeQL (java-kotlin) (push) Waiting to run
Security / CodeQL (javascript-typescript) (push) Waiting to run
Security / CodeQL (python) (push) Waiting to run
fix(skill): harden SkillHub CLI guide bootstrap (#842)
* fix(skill): harden SkillHub CLI guide bootstrap

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>

* test(web): support exact preview browser checks

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>

* test(skill): enforce guide safety contracts

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>

* fix(skill): verify CLI package provenance

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>

* test(web): align CLI provenance assertions

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>

---------

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
2026-09-09 20:25:50 +08:00

100 lines
2.8 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { defineConfig, type Plugin } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import { validateBasePath } from './base-path-config'
const JS_BUILD_TARGET = 'es2020'
const LEGACY_BROWSER_TARGETS = ['chrome83', 'edge83', 'firefox78', 'safari14']
const basePath = validateBasePath(process.env.VITE_BASE_PATH ?? '/')
const guideTemplate = readFileSync(path.resolve(__dirname, 'src/docs/skill.md.template'), 'utf8')
const safeHostPattern = /^(?:[A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\])(?::[0-9]{1,5})?$/
function registryGuidePlugin(): Plugin {
const basePrefix = basePath === '/' ? '' : basePath.slice(0, -1)
const guidePath = `${basePrefix}/registry/skill.md`
const guideTemplatePath = `${basePrefix}/registry/skill.md.template`
return {
name: 'skillhub-cli-guide',
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'registry/skill.md',
source: guideTemplate,
})
},
configureServer(server) {
// Install after Vite's built-in Host check and reject malformed Host
// values consistently with the production route. originalUrl survives
// SPA/base rewrites.
return () => {
server.middlewares.use((request, response, next) => {
const requestPath = new URL(request.originalUrl ?? request.url ?? '/', 'http://localhost').pathname
if (requestPath === guideTemplatePath) {
response.statusCode = 404
response.end('Not Found')
return
}
if (requestPath !== guidePath) {
next()
return
}
const host = request.headers.host
if (!host || !safeHostPattern.test(host)) {
response.statusCode = 400
response.end('Invalid Host')
return
}
response.statusCode = 200
response.setHeader('Content-Type', 'text/markdown; charset=utf-8')
response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
response.end(guideTemplate)
})
}
},
}
}
export default defineConfig({
base: basePath,
plugins: [registryGuidePlugin(), react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
build: {
target: JS_BUILD_TARGET,
cssTarget: LEGACY_BROWSER_TARGETS,
},
optimizeDeps: {
esbuildOptions: {
target: JS_BUILD_TARGET,
},
},
test: {
exclude: ['**/node_modules/**', '**/e2e/**'],
testTimeout: 30000,
hookTimeout: 30000,
},
server: {
port: 3000,
watch: {
usePolling: true,
interval: 150,
},
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
'/oauth2': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})