0d986f60bd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
集成服务器托管模型、自然语言移动与有界抓放、内置 LeKiwi URL 导入和双摄像头;同步部署契约与指定域名 iframe 白名单,保留原有物理安全、会话及调用预算防护。 更新 npm 包及锁文件版本、CHANGELOG 与发布文档。提交前 typecheck、120 项定向前端测试和 44 项后端测试通过(3 项可选跳过);真实 v2 云模型抓放仍待单独验收,不包含运行密钥或构建产物。
159 lines
4.8 KiB
TypeScript
159 lines
4.8 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { defineConfig } from 'vitest/config';
|
|
import type { Plugin } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
|
|
const PYODIDE_ASSETS = [
|
|
'pyodide.asm.js',
|
|
'pyodide.asm.wasm',
|
|
'python_stdlib.zip',
|
|
'pyodide-lock.json',
|
|
] as const;
|
|
const pyodideDirectory = resolve(
|
|
dirname(fileURLToPath(import.meta.url)),
|
|
'../node_modules/pyodide',
|
|
);
|
|
|
|
/** 生产预览中长期缓存带内容哈希的资源,同时允许入口文件及时更新。 */
|
|
function previewCacheHeaders(): Plugin {
|
|
return {
|
|
name: 'preview-cache-headers',
|
|
configurePreviewServer(server) {
|
|
server.middlewares.use((request, response, next) => {
|
|
const path = request.url?.split(/[?#]/, 1)[0] ?? '';
|
|
if (path.startsWith('/assets/'))
|
|
response.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
else if (path.startsWith('/pyodide/'))
|
|
response.setHeader(
|
|
'Cache-Control',
|
|
'public, max-age=86400, stale-while-revalidate=604800',
|
|
);
|
|
else response.setHeader('Cache-Control', 'no-cache');
|
|
next();
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
/** 让开发服务器与生产构建都从本地 npm 包提供 Pyodide,保持平台可离线部署。 */
|
|
function localPyodideAssets(): Plugin {
|
|
let isBuild = false;
|
|
return {
|
|
name: 'local-pyodide-assets',
|
|
configResolved(config) {
|
|
isBuild = config.command === 'build';
|
|
},
|
|
configureServer(server) {
|
|
server.middlewares.use((request, response, next) => {
|
|
const name = request.url?.split(/[?#]/, 1)[0].replace(/^\/pyodide\//, '');
|
|
if (!name || !PYODIDE_ASSETS.includes(name as (typeof PYODIDE_ASSETS)[number])) {
|
|
next();
|
|
return;
|
|
}
|
|
response.setHeader(
|
|
'Content-Type',
|
|
name.endsWith('.wasm')
|
|
? 'application/wasm'
|
|
: name.endsWith('.zip')
|
|
? 'application/zip'
|
|
: name.endsWith('.json')
|
|
? 'application/json'
|
|
: 'text/javascript',
|
|
);
|
|
response.end(readFileSync(resolve(pyodideDirectory, name)));
|
|
});
|
|
},
|
|
buildStart() {
|
|
if (isBuild)
|
|
for (const name of PYODIDE_ASSETS)
|
|
this.emitFile({
|
|
type: 'asset',
|
|
fileName: `pyodide/${name}`,
|
|
source: readFileSync(resolve(pyodideDirectory, name)),
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig(({ mode }) => ({
|
|
root: 'web_platform',
|
|
base: './',
|
|
plugins: [
|
|
react(),
|
|
localPyodideAssets(),
|
|
previewCacheHeaders(),
|
|
...(mode === 'website'
|
|
? [
|
|
{
|
|
name: 'website-html',
|
|
transformIndexHtml(html: string) {
|
|
return html
|
|
.replace(
|
|
'<title>MuJoCo Web 仿真平台</title>',
|
|
'<title>CADWorld Sim · 在线机器人仿真</title>',
|
|
)
|
|
.replace('本地机器人仿真', '浏览器机器人仿真')
|
|
.replace('正在启动本地仿真工作台', '正在启动在线仿真工作台');
|
|
},
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
publicDir: 'public',
|
|
build: {
|
|
outDir: mode === 'website' ? '../website-dist' : '../web-platform-dist',
|
|
emptyOutDir: true,
|
|
target: 'es2022',
|
|
rollupOptions: {
|
|
input: {
|
|
main: resolve(dirname(fileURLToPath(import.meta.url)), 'index.html'),
|
|
...(mode === 'website'
|
|
? {}
|
|
: { tuning: resolve(dirname(fileURLToPath(import.meta.url)), 'tuning.html') }),
|
|
},
|
|
},
|
|
modulePreload: { polyfill: false },
|
|
},
|
|
worker: { format: 'es' },
|
|
server: {
|
|
open: mode !== 'website',
|
|
fs: { allow: ['..'] },
|
|
proxy: {
|
|
'/api/decision/v1': { target: process.env.CADWORLD_DEV_API ?? 'http://127.0.0.1:8768' },
|
|
},
|
|
},
|
|
preview: {
|
|
proxy: {
|
|
'/api/decision/v1': { target: process.env.CADWORLD_DEV_API ?? 'http://127.0.0.1:8768' },
|
|
},
|
|
},
|
|
test: {
|
|
globals: true,
|
|
environment: 'jsdom',
|
|
setupFiles: './src/test/setup.ts',
|
|
include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'html', 'json-summary'],
|
|
include: [
|
|
'src/project/cachedFiles.ts',
|
|
'src/project/importer.ts',
|
|
'src/project/urdfToMjcf.ts',
|
|
'src/project/workspace.ts',
|
|
'src/simulation/geometry.ts',
|
|
'src/simulation/ModelBindings.ts',
|
|
'src/simulation/ControlArbiter.ts',
|
|
'src/robot/**/*.ts',
|
|
'src/stores/useAppStore.ts',
|
|
'src/training/LocalTrainingClient.ts',
|
|
'src/viewer/interactionMath.ts',
|
|
'src/viewer/texturePixels.ts',
|
|
'src/viewer/visualizationMath.ts',
|
|
],
|
|
thresholds: { lines: 85, functions: 75, branches: 70, statements: 80 },
|
|
},
|
|
},
|
|
}));
|