import type { Plugin } from 'vite'; import type { IncomingMessage,ServerResponse } from 'node:http'; import { spawn } from 'node:child_process'; import { existsSync } from 'node:fs'; import { mkdir,readFile,writeFile } from 'node:fs/promises'; import { randomUUID } from 'node:crypto'; import path from 'node:path'; import { resolvePython } from '../pierzainer_ground/python-runtime.ts'; export const DWG_API='/__pierzainer/general-arrangement/dwg'; /** A narrow local export route; no project state or existing drawing-extractor endpoints are changed. */ export function generalArrangementDwgPlugin(root:string):Plugin{ let active=0; const handler=async(req:IncomingMessage,res:ServerResponse)=>{ const error=(status:number,message:string)=>{res.statusCode=status;res.setHeader('Content-Type','application/json; charset=utf-8');res.end(JSON.stringify({error:message}));}; if(req.method!=='POST'){error(405,'DWG 출력은 POST 요청만 지원합니다.');return;} try{if(req.headers.origin&&new URL(req.headers.origin).host!==req.headers.host){error(403,'현재 프로그램에서만 요청할 수 있습니다.');return;}}catch{error(403,'잘못된 요청 출처입니다.');return;} if(active>=2){error(429,'다른 도면을 변환 중입니다. 완료 후 다시 실행하세요.');return;} active++; try{ const chunks:Buffer[]=[];let size=0; for await(const chunk of req){const b=Buffer.from(chunk);size+=b.length;if(size>20*1024*1024)throw new Error('도면 크기가 20MB를 넘습니다.');chunks.push(b);} const body=JSON.parse(Buffer.concat(chunks).toString('utf8')) as {dxf?:unknown}; if(typeof body.dxf!=='string'||!body.dxf.includes('SECTION')||!body.dxf.trim().endsWith('EOF'))throw new Error('유효한 DXF 도면 데이터가 필요합니다.'); const python=resolvePython(root),script=path.join(root,'도면작성','dwg_export.py'); if(path.isAbsolute(python)&&!existsSync(python))throw new Error('DWG 출력용 기존 Python 실행 환경을 찾을 수 없습니다.'); const folder=path.join(root,'도면작성','output',randomUUID());await mkdir(folder,{recursive:true}); const source=path.join(folder,'source.dxf'),output=path.join(folder,'general.dwg');await writeFile(source,body.dxf,'utf8'); const result=await new Promise<{code:number;stdout:string;stderr:string}>((resolve,reject)=>{ const child=spawn(python,['-X','utf8',script,source,output],{cwd:root,windowsHide:true,stdio:['ignore','pipe','pipe']}); let stdout='',stderr='';child.stdout.on('data',b=>stdout+=b);child.stderr.on('data',b=>stderr+=b);child.on('error',reject); const timeout=setTimeout(()=>{child.kill();reject(new Error('DWG 변환 제한 시간(120초)을 넘었습니다.'));},120000); child.on('exit',code=>{clearTimeout(timeout);resolve({code:code??-1,stdout,stderr});}); }); const report=JSON.parse(result.stdout.trim().split(/\r?\n/).at(-1)??'{}'); if(result.code!==0||report.status!=='validated')throw new Error(report.message||'DWG 왕복 검증을 통과하지 못했습니다.'); const bytes=await readFile(output);res.statusCode=200;res.setHeader('Content-Type','application/octet-stream');res.setHeader('Content-Disposition','attachment; filename="general.dwg"');res.setHeader('X-Pierzainer-Validation',encodeURIComponent(JSON.stringify(report)));res.end(bytes); }catch(e){error(422,(e as Error).message);}finally{active--;} }; return{name:'pierzainer-general-arrangement-dwg',configureServer(server){server.middlewares.use(DWG_API,(req,res)=>void handler(req,res));},configurePreviewServer(server){server.middlewares.use(DWG_API,(req,res)=>void handler(req,res));}}; }