import {createServer} from 'vite'; import vue from '@vitejs/plugin-vue'; import {mkdirSync,mkdtempSync,readFileSync,writeFileSync} from 'node:fs'; import {resolve,join} from 'node:path'; import {tmpdir} from 'node:os'; import {fileURLToPath} from 'node:url'; const target=resolve(fileURLToPath(new URL('..',import.meta.url))), source=process.env.OPENMAUS_REFERENCE_ROOT || resolve(target,'../OpenMausBot'), root=mkdtempSync(join(tmpdir(),'yaoyao-avatar-reference-')); mkdirSync(root+'/reference',{recursive:true});mkdirSync(root+'/ported',{recursive:true}); const shapes=['circle','ellipse','square','capsule','triangle','hexagon','cloud','droplet']; const bodies=['cursor','blob','circle','squircle','capsule','drop','shield','hexagon','diamond','star']; const expressions=['idle','happy','curious','drowsy','working','thinking','listening','sleeping','suspicious','proud']; const cases=[...shapes.map(shape=>({id:'shape-'+shape,shape,color:'green',hex:'#00c875',expression:'idle'})),...bodies.map(bodyId=>({id:'body-'+bodyId,shape:'circle',bodyId,color:'blue',hex:'#1488ff',expression:'happy'})),...expressions.map(expression=>({id:'face-'+expression,shape:'circle',color:'pink',hex:'#f52ba5',expression}))]; const html='
'; for(const sub of ['reference','ported'])writeFileSync(root+'/'+sub+'/index.html',html); let cursor=readFileSync(source+'/src/components/CursorAvatar.tsx','utf8'); cursor=cursor.replace('"../../shared/mascot-bodies"',JSON.stringify(source+'/shared/mascot-bodies.ts')).replace('"./cursor-face-data"',JSON.stringify(source+'/src/components/cursor-face-data.ts')); // Canonical resting-frame capture: settle the spring and hold elapsed time at 0. // This only controls the test clock; all original geometry/drawing code is unchanged. cursor=cursor.replace('if (p.paused) {','if (p.paused) { e.morph = 1; e.stateStart = now;'); writeFileSync(root+'/reference/CursorAvatar.tsx',cursor); let avatar=readFileSync(source+'/src/components/Avatar.tsx','utf8').replace('"../../shared/bot-avatar"',JSON.stringify(source+'/shared/bot-avatar.ts')).replace('"../../shared/mascot-bodies"',JSON.stringify(source+'/shared/mascot-bodies.ts')); writeFileSync(root+'/reference/Avatar.tsx',avatar); writeFileSync(root+'/reference/main.tsx',`import React from 'react';import{createRoot}from'react-dom/client';import{MausAvatar}from'./Avatar';const cases=${JSON.stringify(cases)};createRoot(document.getElementById('app')!).render(
{cases.map(c=>
{c.id}
)}
);`); writeFileSync(root+'/ported/main.tsx',`import{createApp,h}from'vue';import AgentAvatar from '${target}/src/client/components/common/AgentAvatar.vue';import{defaultAgentIdentity,encodeAgentAvatar}from'${target}/src/shared/agentIdentity.ts';const cases=${JSON.stringify(cases)};createApp({render:()=>h('div',{class:'grid'},cases.map(c=>h('div',{class:'case','data-testid':c.id},[h(AgentAvatar,{name:c.id,avatar:encodeAgentAvatar({...defaultAgentIdentity(c.id),shape:c.shape,bodyId:c.bodyId??null,color:c.hex,expression:c.expression}),size:96,animated:false,fixedTime:0}),h('span',c.id)])))}).mount('#app');`); writeFileSync(root+'/cases.json',JSON.stringify(cases)); const servers=[]; for(const [sub,port]of[['reference',18806],['ported',18807]]){ const server=await createServer({configFile:false,root:root+'/'+sub,plugins:sub==='ported'?[vue()]:[],resolve:{alias:{'@':sub==='reference'?source+'/src':target+'/src/client','@shared':target+'/src/shared',react:source+'/node_modules/react','react-dom':source+'/node_modules/react-dom',vue:target+'/node_modules/vue'}},server:{host:'127.0.0.1',port,strictPort:true,fs:{allow:[root,source,target]}}}); await server.listen();servers.push(server);console.log(sub,port); } for(const signal of ['SIGINT','SIGTERM'])process.once(signal,async()=>{for(const server of servers)await server.close();process.exit()});