@fps-games/editor-deployment 0.2.3-beta.17 → 0.2.3-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -10,10 +10,10 @@
10
10
  html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: #101018; }
11
11
  #renderCanvas { width: 100%; height: 100%; display: block; outline: none; }
12
12
  </style>
13
- <script type="importmap">{"imports":{"@fps-games/editor/playable-sdk":"./assets/playable-sdk-D42v8kWc.js","@babylonjs/core":"./assets/babylon-core-DXA-HGqT.js","@fps-games/editor-core/plugin-host":"./assets/editor-core-plugin-host-94e73c63.js"}}</script>
14
- <script type="module" crossorigin src="./assets/index-ChyK2lhc.js"></script>
13
+ <script type="importmap">{"imports":{"@fps-games/editor/playable-sdk":"./assets/playable-sdk-A7Cfp-Qu.js","@babylonjs/core":"./assets/babylon-core-DXA-HGqT.js","@fps-games/editor-core/plugin-host":"./assets/editor-core-plugin-host-94e73c63.js"}}</script>
14
+ <script type="module" crossorigin src="./assets/index-DTqLoSGs.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="./assets/hemisphericLight-C4swwL68.js">
16
- <link rel="modulepreload" crossorigin href="./assets/playable-standard-project-DgsY-0An.js">
16
+ <link rel="modulepreload" crossorigin href="./assets/playable-standard-project-bxE3z_Fn.js">
17
17
  </head>
18
18
  <body>
19
19
  </body>
Binary file
Binary file
@@ -1,4 +1,4 @@
1
- import{readFile as Tt}from"node:fs/promises";import Me from"node:path";import{createHash as qe,randomBytes as Ee,randomUUID as Oe}from"node:crypto";import{once as be}from"node:events";import{createReadStream as et}from"node:fs";import{mkdir as tt,open as Te,readFile as me,realpath as ue,rm as xe,stat as pe}from"node:fs/promises";import{createServer as rt}from"node:http";import{spawn as nt}from"node:child_process";import S from"node:path";var P="/__fps_editor_authoring",ot=64*1024,B="/__fps_editor/runtime-tuning/v1",it=16*1024,Re=15e3;async function De(e){let r=await ue(e.projectRoot),o=e.projectEpoch?.trim()||qe("sha256").update(r).digest("hex").slice(0,32),a=e.host?.trim()||"127.0.0.1",f=_t(e.port??3012),p=gt(a,f),O=S.resolve(r,e.workerEntry);if(Fe(r,O,"worker entry"),!(await pe(O).catch(()=>null))?.isFile())throw new Error(`Editor Project I/O worker entry is missing: ${O}`);let h=dt(e.projectDefinition),y=st(e.pluginSnapshots),_=new Map(e.builds.map(t=>[t.release,ct(r,t)]));if(_.size===0||_.size!==e.builds.length)throw new Error("Editor Server requires uniquely named built Editor releases.");let E=e.activeRelease?.trim()||e.builds[0].release;if(!_.has(E))throw new Error(`Unknown active Editor release: ${E}`);let m=e.logger??console,F=Oe(),re=new Date().toISOString(),ne=e.testControl?e.controlToken?.trim()||Ee(32).toString("base64url"):null,V=e.runtimeUrl?.trim()||"http://127.0.0.1:3011",K=new URL(V).origin,oe=Ee(32).toString("base64url"),T=new Map,w=null,Ue=te(e.workerReadyTimeoutMs,3e4),Q=te(e.workerHeartbeatTimeoutMs,1e4),ze=te(e.workerRestartLimit,3),Ge=te(e.workerRestartWindowMs,6e4),_e=at(e.workerWatchdogMode),ie=S.join(r,".fps-editor/state/editor-server.owner"),ge=await ut(ie,F),I=null,X=new WeakSet,ae=new WeakMap,U=!1,R="starting",A=0,z=null,G=null,se=0,x=[],ce=null,$=null,Z=Date.now(),we=null,D=null,N=!1,de,W=null,k=null,M=rt((t,n)=>{Be(t,n).catch(i=>{m.error?.(`[fps-editor-server] request failed: ${j(i)}`),n.headersSent?n.destroy(i instanceof Error?i:void 0):d(n,500,{ok:!1,error:j(i)})})});M.on("error",t=>m.error?.(`[fps-editor-server] ${j(t)}`));try{await new Promise((t,n)=>{M.once("error",n),M.listen(f,a,()=>{M.off("error",n),t()})}),await le().catch(t=>{m.error?.(`[fps-editor-server] initial Project I/O startup failed: ${j(t)}`)})}catch(t){throw await Pe(M).catch(()=>{}),await ke(ie,ge).catch(()=>{}),t}Z=Date.now(),W=setInterval(()=>{let t=Date.now(),n=t-Z;if(Z=t,n>Q){we=n,D=t+Q,N=!1,m.info?.(`[fps-editor-server] Supervisor resumed after a ${n}ms gap; granting Project I/O one heartbeat grace period.`);return}if(!(U||R!=="ready"||!$||!I)){if(t-Date.parse($)<=Q){D=null,N=!1;return}if(!(D!==null&&t<=D)){if(D=null,_e==="observe"){N||(N=!0,m.warn?.("[fps-editor-server] Project I/O heartbeat timed out; observe mode left it running."));return}m.warn?.("[fps-editor-server] Project I/O heartbeat timed out; restarting worker."),N=!0,R="restarting",ae.set(I,"heartbeat-timeout"),I.kill("SIGKILL")}}},Math.min(1e3,Math.max(100,Math.floor(Q/3)))),W.unref?.(),m.info?.(`[fps-editor-server] ready at ${p}`);function L(){return Ie(Date.now()),Object.freeze({status:R,ready:R==="ready",endpointBase:P,generation:A,pid:I?.pid??null,serviceInstanceId:z,restartCount:se,recentFailureCount:x.length,watchdogMode:_e,lastSupervisorTickAt:new Date(Z).toISOString(),lastSuspendGapMs:we,lastRestartReason:ce,lastHeartbeatAt:$,...de?{lastExit:de}:{}})}function Y(){let t=_.get(E),n=L();return{ok:!0,authority:"browser",serverInstanceId:F,pid:process.pid,startedAt:re,projectEpoch:o,host:a,port:f,projectIO:n,worker:n,editor:{activeRelease:E,artifactId:t.artifactId,build:t.build}}}async function le(){if(U)return;A+=1,R=A===1?"starting":"restarting",z=null,G=null,$=null,D=null,N=!1;let t=nt(process.execPath,[O],{cwd:r,stdio:["ignore","inherit","inherit","ipc"],env:{...process.env,FPS_EDITOR_WORKER_GENERATION:String(A),FPS_EDITOR_WORKER_PROJECT_DEFINITION:JSON.stringify(h),FPS_EDITOR_WORKER_PLUGIN_SNAPSHOTS:JSON.stringify(y)}});I=t,t.on("message",c=>{We(t,c).catch(l=>{m.error?.(`[fps-editor-server] invalid Project I/O worker message: ${j(l)}`),t.kill("SIGKILL")})}),t.once("exit",(c,l)=>{Le(t,c,l)});let n=Date.now()+Ue,i=()=>R==="ready";for(;!U&&I===t&&!i()&&Date.now()<n;)await new Promise(c=>setTimeout(c,25));if(!i())throw I===t&&t.exitCode===null&&t.signalCode===null&&(R="restarting",ae.set(t,"worker-exit"),t.kill("SIGKILL")),new Error("Editor Project I/O worker did not become ready before the startup deadline.")}async function We(t,n){if(t!==I||!mt(n))return;if(n.type==="heartbeat"){n.serviceInstanceId===z&&($=new Date().toISOString());return}let i=new URL(n.internalUrl);if(i.protocol!=="http:"||i.hostname!=="127.0.0.1")throw new Error("Editor Project I/O worker internal URL must use loopback HTTP.");z=n.serviceInstanceId,G=i.toString(),$=new Date().toISOString(),D=null,N=!1,R="ready"}async function Le(t,n,i){let c=X.has(t);X.delete(t),I===t&&(I=null);let l=Date.now();if(de={code:n,signal:i,at:new Date(l).toISOString()},G=null,z=null,U||c){R="stopped";return}if(ce=ae.get(t)??"worker-exit",se+=1,x.push(l),Ie(l),x.length>ze){R="fused",m.error?.("[fps-editor-server] Project I/O worker restart fuse opened.");return}R="restarting";let g=[100,500,2e3][Math.min(x.length-1,2)];k=setTimeout(()=>{k=null,le().catch(u=>{m.error?.(`[fps-editor-server] Project I/O worker restart failed: ${j(u)}`)})},g),k.unref?.()}function Ie(t){let n=t-Ge;x=x.filter(i=>i>=n)}async function Be(t,n){let i=new URL(t.url??"/",p);if(pt(n),t.method==="GET"&&i.pathname==="/__fps_editor/health"){d(n,200,Y());return}if(t.method==="GET"&&i.pathname==="/__fps_editor/status"){d(n,200,Y());return}if(t.method==="GET"&&i.pathname==="/__fps_editor/project-io/health"){let c=L();d(n,c.ready?200:503,{ok:c.ready,projectIO:c});return}if(t.method==="GET"&&i.pathname===`${P}/health`){let c=L();d(n,200,{schemaVersion:1,kind:"project-io",ready:c.ready,status:c.ready?"ready":"unavailable",generation:c.generation,serviceInstanceId:c.serviceInstanceId,...c.ready?{}:{reason:c.status}});return}if(t.method==="GET"&&i.pathname==="/__fps_editor/bootstrap.json"){let c=_.get(E),l=L();d(n,200,{ok:!0,schemaVersion:1,authority:"browser",serverInstanceId:F,projectEpoch:o,workerGeneration:A,authoringEndpointBase:P,projectDefinition:h,pluginSnapshots:y,projectIO:{endpointBase:l.endpointBase,status:l.ready?"ready":"unavailable",generation:l.generation,serviceInstanceId:l.serviceInstanceId,...l.ready?{}:{reason:l.status}},runtimeUrl:V,playUrl:V,build:c.build});return}if(i.pathname.startsWith(`${B}/`)){await Ve(t,n,i);return}if((t.method==="GET"||t.method==="HEAD")&&(i.pathname==="/"||i.pathname==="/index.html"||i.pathname==="/__fps_editor/document")){await Je(n,i.searchParams.get("instanceId"),i.searchParams.get("release"),t.method==="HEAD");return}if((t.method==="GET"||t.method==="HEAD")&&i.pathname.startsWith("/__fps_editor/build/")){await Ke(t,n,i);return}if((t.method==="GET"||t.method==="HEAD")&&i.pathname.startsWith("/src/")){await Qe(t,n,i);return}if(t.method==="POST"&&i.pathname.startsWith("/__fps_editor/control/")){await Ze(t,n,i.pathname);return}if(i.pathname===P||i.pathname.startsWith(`${P}/`)){await Xe(t,n,i);return}d(n,404,{ok:!1,error:"not_found"})}async function Je(t,n,i,c=!1){let l=_.get(E);if(i?.trim()&&i.trim()!==E){d(t,409,{ok:!1,error:"editor_build_incompatible",activeRelease:E,requestedRelease:i.trim(),activeBuild:l.build});return}let g=await me(S.join(l.rootPath,"index.html"),"utf8"),u=`/__fps_editor/build/${encodeURIComponent(l.artifactId)}/`,s=L(),v=[`<base href="${wt(u)}">`,"<script>","window.__FPS_EDITOR_HOSTED_SANDBOX__=true;",'window.__BOOT_MODE="edit";',`window.__FPS_EDITOR_SERVER__=${H({serverInstanceId:F,instanceId:n?.trim()||null,release:l.release,artifactId:l.artifactId,build:l.build})};`,`window.__FPS_EDITOR_PROJECT_IO__=${H({endpointBase:P,status:s.ready?"ready":"unavailable",generation:s.generation,serviceInstanceId:s.serviceInstanceId,...s.ready?{}:{reason:s.status}})};`,`window.__FPS_EDITOR_PROJECT_DEFINITION__=${H(h)};`,`window.__FPS_EDITOR_PLUGIN_SNAPSHOTS__=${H(y)};`,`window.__FPS_EDITOR_RUNTIME_URL__=${H(V)};`,`window.__FPS_EDITOR_RUNTIME_TUNING__=${H({endpointBase:B,token:oe,projectId:je(h)})};`,"window.__FPS_EDITOR_PROJECT_IO_MONITOR__=setInterval(async()=>{",'try{const response=await fetch("/__fps_editor_authoring/health",{cache:"no-store"});','if(!response.ok)throw new Error("project-io-health-failed");const health=await response.json();const previous=window.__FPS_EDITOR_PROJECT_IO__;',`const next={endpointBase:"${P}",status:health.status,`,"generation:health.generation,serviceInstanceId:health.serviceInstanceId,reason:health.reason};","if(previous?.status===next.status&&previous?.generation===next.generation&&previous?.reason===next.reason)return;","window.__FPS_EDITOR_PROJECT_IO__=next;",'dispatchEvent(new CustomEvent("fps-editor:project-io-availability-changed",{detail:next}));',`}catch{const previous=window.__FPS_EDITOR_PROJECT_IO__;const next={endpointBase:"${P}",`,'status:"unavailable",generation:previous?.generation??0,serviceInstanceId:previous?.serviceInstanceId??null,','reason:"Project I/O health request failed."};',"if(previous?.status===next.status&&previous?.generation===next.generation&&previous?.reason===next.reason)return;","window.__FPS_EDITOR_PROJECT_IO__=next;",'dispatchEvent(new CustomEvent("fps-editor:project-io-availability-changed",{detail:next}));}',"},1000);",'addEventListener("pagehide",()=>clearInterval(window.__FPS_EDITOR_PROJECT_IO_MONITOR__),{once:true});',"</script>"].join(""),ee=/<head[^>]*>/i.test(g)?g.replace(/<head[^>]*>/i,Ye=>`${Ye}${v}`):`${v}${g}`;t.statusCode=200,t.setHeader("Cache-Control","no-store"),t.setHeader("Content-Type","text/html; charset=utf-8"),t.end(c?void 0:ee)}async function Ve(t,n,i){let c=i.pathname;if(t.method==="OPTIONS"){if(t.headers.origin!==K)return d(n,404,{ok:!1,error:"not_found"});n.statusCode=204,n.setHeader("Access-Control-Allow-Origin",K),n.setHeader("Access-Control-Allow-Methods","POST"),n.setHeader("Access-Control-Allow-Headers","content-type"),n.setHeader("Vary","Origin"),n.end();return}if(c===`${B}/consumer/poll`){let u=Ae(t);if(t.method!=="POST"||t.headers["x-fps-editor-runtime-tuning-token"]!==oe||!u)return d(n,404,{ok:!1,error:"not_found"});if(T.has(u)){d(n,409,{ok:!1,error:"runtime_tuning_consumer_busy"});return}let s={id:u,response:n,delivered:!1,timer:setTimeout(()=>{T.get(u)===s&&T.delete(u),d(n,204,{})},Re)};T.set(u,s),n.once("close",()=>{!s.delivered&&T.get(u)===s&&(clearTimeout(s.timer),T.delete(u))}),w&&T.size===1&&ye(w);return}if(c===`${B}/consumer/result`){let u=Ae(t);if(t.method!=="POST"||t.headers["x-fps-editor-runtime-tuning-token"]!==oe||!u)return d(n,404,{ok:!1,error:"not_found"});let s=await Ce(t),v=typeof s.requestId=="string"?s.requestId:"";if(!w||v!==w.id||u!==w.consumerId)return d(n,409,{ok:!1,error:"runtime_tuning_stale_result"});let ee=w;w=null,T.delete(u),clearTimeout(ee.timer),ee.settle(Ot(s.result)),d(n,200,{ok:!0});return}if(c!==`${B}/submit`||t.method!=="POST"||t.headers.origin!==K||!t.headers["content-type"]?.toLowerCase().startsWith("application/json"))return d(n,404,{ok:!1,error:"not_found"});n.setHeader("Access-Control-Allow-Origin",K),n.setHeader("Vary","Origin");let l;try{l=It(await Ce(t),je(h))}catch(u){return d(n,400,{ok:!1,error:"runtime_tuning_invalid_intent",message:j(u).slice(0,240)})}if(T.size===0)return d(n,409,{ok:!1,error:"runtime_tuning_no_consumer"});if(T.size>1)return d(n,409,{ok:!1,error:"runtime_tuning_multiple_consumers",message:"\u68C0\u6D4B\u5230\u591A\u4E2A\u5F53\u524D\u9879\u76EE\u7F16\u8F91\u5668\u3002\u8BF7\u53EA\u4FDD\u7559\u4E00\u4E2A\u7F16\u8F91\u5668\u9875\u9762\u540E\u91CD\u8BD5\u3002"});if(w)return d(n,409,{ok:!1,error:"runtime_tuning_busy"});let g=await new Promise(u=>{let s={id:Oe(),intent:l,settle:u,consumerId:null,delivered:!1,timer:setTimeout(()=>{w===s&&(w=null),s.consumerId&&T.delete(s.consumerId),u({ok:!1,code:s.delivered?"runtime_tuning_outcome_unknown":"runtime_tuning_no_consumer",message:s.delivered?"\u7F16\u8F91\u5668\u5DF2\u6536\u5230\u8BF7\u6C42\uFF0C\u4F46\u7ED3\u679C\u5728\u9650\u5B9A\u65F6\u95F4\u5185\u672A\u77E5\u3002":"\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u7F16\u8F91\u5668\u3002"})},Re)};w=s,ye(s)});d(n,g.ok===!0?200:409,g)}function ye(t){if(T.size!==1)return;let n=T.values().next().value;!n||n.delivered||(clearTimeout(n.timer),n.delivered=!0,t.consumerId=n.id,t.delivered=!0,d(n.response,200,{ok:!0,requestId:t.id,intent:t.intent}))}async function Ke(t,n,i){let c=i.pathname.split("/").filter(Boolean),l=decodeURIComponent(c[2]??""),g=[..._.values()].find(s=>s.artifactId===l);if(!g){d(n,404,{ok:!1,error:"editor_build_not_found"});return}let u=c.slice(3).map(decodeURIComponent).join("/");await Se(t,n,g.rootPath,u,!0)}async function Qe(t,n,i){let c=decodeURIComponent(i.pathname.slice(1));await Se(t,n,r,c,!1,i.searchParams.has("v"))}async function Xe(t,n,i){if(!G||R!=="ready"){d(n,503,{ok:!1,error:"editor_project_io_unavailable",generation:A});return}let c;try{c=t.method==="GET"||t.method==="HEAD"?void 0:await Ne(t,i.pathname===`${P}/asset-import`?ot:128*1024*1024)}catch(s){if(s?.code!=="asset_import_payload_too_large")throw s;d(n,413,{ok:!1,error:"asset_import_payload_too_large",code:"asset_import_payload_too_large",details:s.details});return}let l={};for(let s of["accept","content-type","if-none-match","range","origin","access-control-request-method","access-control-request-headers"]){let v=t.headers[s];typeof v=="string"&&(l[s]=v)}let g;try{g=await fetch(new URL(`${i.pathname}${i.search}`,G),{method:t.method,headers:l,...c?{body:new Uint8Array(c)}:{}})}catch(s){m.warn?.(`[fps-editor-server] Project I/O request failed: ${j(s)}`),d(n,503,{ok:!1,error:"editor_project_io_unavailable",generation:A});return}n.statusCode=g.status;for(let s of["content-type","content-length","etag","content-range","accept-ranges","server-timing","access-control-allow-origin","access-control-allow-methods","access-control-allow-headers","access-control-expose-headers","vary"]){let v=g.headers.get(s);v&&n.setHeader(s,v)}let u=t.method==="HEAD"?null:Buffer.from(await g.arrayBuffer());n.setHeader("Cache-Control","no-store"),n.end(u??void 0)}async function Ze(t,n,i){if(!ne||t.headers["x-fps-editor-control-token"]!==ne){d(n,404,{ok:!1,error:"not_found"});return}if(i==="/__fps_editor/control/worker/crash"){if(!I){d(n,409,{ok:!1,error:"worker_unavailable"});return}I.kill("SIGKILL"),d(n,202,{ok:!0});return}if(i==="/__fps_editor/control/worker/heartbeat/stop"||i==="/__fps_editor/control/worker/heartbeat/start"){if(!I?.connected){d(n,409,{ok:!1,error:"worker_unavailable"});return}I.send({type:i.endsWith("/stop")?"test.stop-heartbeat":"test.start-heartbeat"}),d(n,200,{ok:!0});return}let c="/__fps_editor/control/activate/",l=decodeURIComponent(i.slice(c.length));if(i.startsWith(c)&&_.has(l)){let g=E;if(E=l,se=0,x=[],ce=null,k&&(clearTimeout(k),k=null),I){let u=I;R="restarting",X.add(u);let s=be(u,"exit");u.kill("SIGTERM"),await s}await le().catch(u=>{m.error?.(`[fps-editor-server] Project I/O activation restart failed: ${j(u)}`)}),d(n,200,{ok:!0,previousRelease:g,activeRelease:E,status:Y()});return}d(n,404,{ok:!1,error:"control_not_found"})}let q=null;return Object.freeze({url:p,controlToken:ne,status:Y,dispose(){return q||(q=(async()=>{U=!0;for(let n of T.values())clearTimeout(n.timer),n.response.writableEnded||d(n.response,503,{ok:!1,code:"runtime_tuning_editor_unavailable",message:"\u7F16\u8F91\u5668\u670D\u52A1\u6B63\u5728\u5173\u95ED\u3002"});T.clear(),w&&(clearTimeout(w.timer),w.settle({ok:!1,code:w.delivered?"runtime_tuning_outcome_unknown":"runtime_tuning_editor_unavailable",message:w.delivered?"\u7F16\u8F91\u5668\u5DF2\u6536\u5230\u8BF7\u6C42\uFF0C\u4F46\u670D\u52A1\u5173\u95ED\u524D\u672A\u8FD4\u56DE\u53EF\u4FE1\u7ED3\u679C\u3002":"\u7F16\u8F91\u5668\u670D\u52A1\u6B63\u5728\u5173\u95ED\u3002"}),w=null),W&&clearInterval(W),k&&clearTimeout(k),W=null,k=null;let t=I;if(t){X.add(t);let n=be(t,"exit").catch(()=>[]);t.kill("SIGTERM"),await Promise.race([n,new Promise(i=>setTimeout(i,5e3))]),t.exitCode===null&&t.signalCode===null&&t.kill("SIGKILL")}await Pe(M),await ke(ie,ge)})(),q)}})}function at(e){if(e===void 0||e==="restart")return"restart";if(e==="observe")return"observe";throw new Error(`Editor Project I/O watchdog mode must be restart or observe, got ${String(e)}.`)}function st(e){if(e===void 0)return Object.freeze({});if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Editor Server Plugin snapshots must be an object.");return Object.freeze(structuredClone(e))}function ct(e,r){let o=S.resolve(e,r.rootPath);if(Fe(e,o,"built Editor root"),!r.release.trim()||!r.artifactId.trim())throw new Error("Editor build identity is incomplete.");return Object.freeze({...r,rootPath:o,build:Object.freeze({...r.build})})}function dt(e){if(!e||typeof e!="object"||Array.isArray(e)||e.schemaVersion!==1||typeof e.projectId!="string"||typeof e.projectName!="string"||!e.authoring||!Array.isArray(e.plugins))throw new Error("Editor Server Project definition is invalid.");return structuredClone(e)}async function Se(e,r,o,a,f,p=!1){if(!a||a.includes("\\")||a.split("/").includes("..")){d(r,404,{ok:!1,error:"file_not_found"});return}let O=await ue(o).catch(()=>null),b=S.resolve(o,a),h=O?S.relative(O,b):"..";if(!O||!h||h===".."||h.startsWith(`..${S.sep}`)||S.isAbsolute(h)){d(r,404,{ok:!1,error:"file_not_found"});return}let y=await ue(b).catch(()=>null);if(!y||y!==b){d(r,404,{ok:!1,error:"file_not_found"});return}let _=await pe(y).catch(()=>null);if(!_?.isFile()){d(r,404,{ok:!1,error:"file_not_found"});return}let E=e.headers["accept-encoding"]??"",m=await lt(y,E);r.statusCode=200,r.setHeader("Cache-Control",f||p?"public, max-age=31536000, immutable":"no-cache"),r.setHeader("Content-Type",ht(y)),r.setHeader("Content-Length",String(m?.stat.size??_.size)),m&&(r.setHeader("Content-Encoding",m.encoding),r.setHeader("Vary","Accept-Encoding")),e.method==="HEAD"?r.end():et(m?.path??y).pipe(r)}async function lt(e,r){for(let o of ve(r,"br")?[{suffix:".br",encoding:"br"},{suffix:".gz",encoding:"gzip"}]:[{suffix:".gz",encoding:"gzip"}]){if(o.encoding==="gzip"&&!ve(r,"gzip"))continue;let a=`${e}${o.suffix}`,f=await pe(a).catch(()=>null);if(f?.isFile())return{path:a,encoding:o.encoding,stat:f}}return null}function ve(e,r){let o=typeof e=="string"?e:e.join(","),a=null;for(let f of o.toLowerCase().split(",")){let[p="",...O]=f.split(";").map(_=>_.trim());if(p!==r&&p!=="*")continue;let b=O.find(_=>_.startsWith("q=")),h=b?Number(b.slice(2)):1,y=Number.isFinite(h)?h:0;if(p===r)return y>0;a=y}return(a??0)>0}async function ut(e,r){await tt(S.dirname(e),{recursive:!0,mode:448});let o={pid:process.pid,serverInstanceId:r,createdAt:new Date().toISOString()};try{let a=await Te(e,"wx",384);return await a.writeFile(`${JSON.stringify(o)}
2
- `,"utf8"),await a.close(),o}catch{let f=await me(e,"utf8").then(JSON.parse).catch(()=>null);if(typeof f?.pid=="number"&&ft(f.pid))throw new Error(`Editor Server project lock is already owned by PID ${f.pid}.`);await xe(e,{force:!0});let p=await Te(e,"wx",384);return await p.writeFile(`${JSON.stringify(o)}
3
- `,"utf8"),await p.close(),o}}async function ke(e,r){(await me(e,"utf8").then(JSON.parse).catch(()=>null))?.serverInstanceId===r.serverInstanceId&&await xe(e,{force:!0})}function ft(e){try{return process.kill(e,0),!0}catch{return!1}}function mt(e){if(!e||typeof e!="object")return!1;let r=e;return r.type==="heartbeat"?typeof r.serviceInstanceId=="string":r.type==="ready"&&typeof r.serviceInstanceId=="string"&&typeof r.internalUrl=="string"}async function Ne(e,r){let o=[],a=0;for await(let f of e){let p=Buffer.isBuffer(f)?f:Buffer.from(f);if(a+=p.byteLength,a>r)throw Object.assign(new Error("asset_import_payload_too_large"),{code:"asset_import_payload_too_large",details:{maxBytes:r}});o.push(p)}return Buffer.concat(o)}function pt(e){e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Access-Control-Expose-Headers","content-length, server-timing"),e.setHeader("Cross-Origin-Resource-Policy","cross-origin"),e.setHeader("Cross-Origin-Embedder-Policy","credentialless"),e.setHeader("X-Content-Type-Options","nosniff")}function d(e,r,o){e.statusCode=r,e.setHeader("Cache-Control","no-store"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.end(`${JSON.stringify(o)}
4
- `)}function ht(e){switch(S.extname(e).toLowerCase()){case".js":case".mjs":return"text/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".html":return"text/html; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".glb":return"model/gltf-binary";case".gltf":return"model/gltf+json";default:return"application/octet-stream"}}function Fe(e,r,o){let a=S.relative(e,r);if(!a||a===".."||a.startsWith(`..${S.sep}`)||S.isAbsolute(a))throw new Error(`Editor Server ${o} must be inside the canonical project root.`)}function _t(e){if(!Number.isSafeInteger(e)||e<1||e>65535)throw new Error("Editor Server port is invalid.");return e}function te(e,r){return Number.isSafeInteger(e)&&e>0?e:r}function gt(e,r){return`http://${e==="0.0.0.0"?"127.0.0.1":e}:${r}`}function H(e){return JSON.stringify(e).replace(/</g,"\\u003c").replace(/>/g,"\\u003e")}function wt(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}async function Pe(e){e.listening&&await new Promise((r,o)=>{e.close(a=>a?o(a):r())})}function j(e){return e instanceof Error?e.message:String(e)}function je(e){let r=e.projectId;return typeof r=="string"&&r.trim()?r.trim():"project"}async function Ce(e){let r=await Ne(e,it);if(!r||r.length===0)throw new Error("Runtime tuning body is required.");let o=JSON.parse(r.toString("utf8"));if(!o||typeof o!="object"||Array.isArray(o))throw new Error("Runtime tuning body must be a JSON object.");return o}function Ae(e){let r=e.headers["x-fps-editor-runtime-tuning-consumer"];return typeof r=="string"&&/^[a-zA-Z0-9_-]{8,96}$/u.test(r)?r:null}function It(e,r){if(!fe(e,["projectId","kind","targets"]))throw new Error("Runtime tuning envelope contains unsupported fields.");if(e.projectId!==r)throw new Error("Runtime tuning project identity does not match.");let o=e.kind,a=e.targets;if(o!=="camera"&&o!=="light"||!Array.isArray(a)||a.length!==(o==="camera"?1:2))throw new Error("Runtime tuning intent is outside the fixed Camera/Light schema.");let f=o==="camera"?new Set(["camera"]):new Set(["environment-light","directional-light"]),p=new Set;for(let O of a){if(!O||typeof O!="object"||Array.isArray(O))throw new Error("Runtime tuning target is invalid.");let b=O;if(!fe(b,["role","targetId","sourceId","changes"]))throw new Error("Runtime tuning target contains unsupported fields.");let h=typeof b.role=="string"?b.role:"";if(!f.has(h)||p.has(h))throw new Error("Runtime tuning target role is invalid.");p.add(h);let y=b.targetId,_=typeof y=="string"?y.trim():"",E=typeof b.sourceId=="string"?b.sourceId.trim():"",m=b.changes;if(!_||_.length>160||!E||E.length>160||!m||typeof m!="object"||Array.isArray(m)||Object.keys(m).length===0||Object.keys(m).length>24)throw new Error("Runtime tuning target is invalid.");for(let[F,re]of Object.entries(m))if(!Et(h,F,re))throw new Error("Runtime tuning intent contains an invalid field.")}if(p.size!==f.size)throw new Error("Runtime tuning target roles are incomplete.");return{projectId:r,kind:o,targets:a}}var yt=new Set(["camera.alpha","camera.beta","camera.radius","camera.orthoSize","camera.fov","camera.minZ","camera.maxZ","camera.inertia"]);function Et(e,r,o){if(e==="camera")return r==="camera.projection"?o==="orthographic"||o==="perspective":r==="camera.targetOffset"?J(o,["x","y","z"]):r==="camera.targetScreenOffset"?J(o,["x","y"]):yt.has(r)&&typeof o=="number"&&Number.isFinite(o);if(e==="environment-light")return r==="enabled"?typeof o=="boolean":r==="light.intensity"?typeof o=="number"&&Number.isFinite(o):r==="light.diffuseColor"||r==="light.groundColor"?J(o,["r","g","b"]):!1;if(e==="directional-light"){if(r==="enabled")return typeof o=="boolean";if(r==="light.intensity")return typeof o=="number"&&Number.isFinite(o);if(r==="light.diffuseColor")return J(o,["r","g","b"]);if(r==="transform.rotation")return J(o,["x","y","z"])}return!1}function J(e,r){if(!e||typeof e!="object"||Array.isArray(e))return!1;let o=e;return fe(o,r)&&r.every(a=>typeof o[a]=="number"&&Number.isFinite(o[a]))}function fe(e,r){let o=new Set(r);return Object.keys(e).every(a=>o.has(a))}function Ot(e){let r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=r.receipt&&typeof r.receipt=="object"&&!Array.isArray(r.receipt)?r.receipt:null;return{ok:r.ok===!0,...typeof r.code=="string"?{code:r.code.slice(0,96)}:{},...typeof r.message=="string"?{message:bt(r.message)}:{},...r.phase==="apply"||r.phase==="save"?{phase:r.phase}:{},...typeof r.draftApplied=="boolean"?{draftApplied:r.draftApplied}:{},...typeof r.dirty=="boolean"?{dirty:r.dirty}:{},...o?{receipt:Object.fromEntries(Object.entries(o).filter(([a,f])=>["version","updatedAt","sceneUrl"].includes(a)&&(typeof f=="string"||typeof f=="number")))}:{}}}function bt(e){return e.replace(/\/(?:Users|home|private|var|tmp)(?:\/[^\s"'<>]+)+/giu,"[redacted-path]").replace(/\b([a-z-]*(?:token|secret|authorization)[a-z-]*)\s*[:=]\s*[^\s,;]+/giu,"$1=[redacted]").replace(/\b[A-Z]:\\(?:[^\s"'<>]+\\)*[^\s"'<>]+/gu,"[redacted-path]").slice(0,320)}Rt();var He=Me.resolve(process.cwd()),C=JSON.parse(await Tt(Me.join(He,".fps-editor/current.json"),"utf8").catch(e=>{throw new Error("Editor deployment is not prepared. Run npm run prepare:server first.",{cause:e})}));if(C.schemaVersion!==4||typeof C.workerEntry!="string"||!C.projectDefinition||!Array.isArray(C.builds))throw new Error("Editor deployment state is invalid. Run npm run prepare:server again.");var he=await De({projectRoot:He,workerEntry:C.workerEntry,projectDefinition:C.projectDefinition,pluginSnapshots:C.pluginSnapshots,builds:C.builds,activeRelease:process.env.FPS_EDITOR_RELEASE?.trim()||"a",host:process.env.FPS_EDITOR_SERVER_HOST?.trim()||"127.0.0.1",port:St(process.env.FPS_EDITOR_SERVER_PORT,3012),runtimeUrl:process.env.FPS_EDITOR_RUNTIME_URL?.trim()||"http://127.0.0.1:3011",workerReadyTimeoutMs:6e4,workerHeartbeatTimeoutMs:1e4,workerRestartLimit:3,workerRestartWindowMs:6e4,workerWatchdogMode:vt(process.env.FPS_EDITOR_WORKER_WATCHDOG),testControl:process.env.FPS_EDITOR_SERVER_TEST_CONTROL==="true",controlToken:process.env.FPS_EDITOR_SERVER_CONTROL_TOKEN});he.controlToken&&console.log(`[fps-editor-server] test control token: ${he.controlToken}`);var $e=!1;for(let e of["SIGINT","SIGTERM"])process.once(e,()=>{$e||($e=!0,he.dispose().finally(()=>{process.exitCode=e==="SIGINT"?130:143}))});function Rt(){let e=Number.parseInt(process.versions.node.split(".")[0]??"",10);if(!Number.isSafeInteger(e)||e<24||e>=27)throw new Error(`fps-editor-server requires Node.js >=24 <27; current version is ${process.versions.node}.`)}function St(e,r){if(!e?.trim())return r;let o=Number(e);if(!Number.isSafeInteger(o)||o<1||o>65535)throw new Error(`FPS_EDITOR_SERVER_PORT must be a valid port, got ${e}.`);return o}function vt(e){if(!e?.trim()||e.trim()==="restart")return"restart";if(e.trim()==="observe")return"observe";throw new Error(`FPS_EDITOR_WORKER_WATCHDOG must be restart or observe, got ${e}.`)}
1
+ import{readFile as Tt}from"node:fs/promises";import Me from"node:path";import{createHash as qe,randomBytes as Oe,randomUUID as be}from"node:crypto";import{once as Te}from"node:events";import{createReadStream as et}from"node:fs";import{mkdir as tt,open as Re,readFile as pe,realpath as fe,rm as xe,stat as he}from"node:fs/promises";import{createServer as rt}from"node:http";import{spawn as nt}from"node:child_process";import S from"node:path";var P="/__fps_editor_authoring",ot=64*1024,H="/__fps_editor/runtime-tuning/v1",it=16*1024,Se=15e3;async function De(e){let r=await fe(e.projectRoot),o=e.projectEpoch?.trim()||qe("sha256").update(r).digest("hex").slice(0,32),a=e.host?.trim()||"127.0.0.1",f=_t(e.port??3012),p=gt(a,f),O=S.resolve(r,e.workerEntry);if(Fe(r,O,"worker entry"),!(await he(O).catch(()=>null))?.isFile())throw new Error(`Editor Project I/O worker entry is missing: ${O}`);let h=dt(e.projectDefinition),y=st(e.pluginSnapshots),_=new Map(e.builds.map(t=>[t.release,ct(r,t)]));if(_.size===0||_.size!==e.builds.length)throw new Error("Editor Server requires uniquely named built Editor releases.");let E=e.activeRelease?.trim()||e.builds[0].release;if(!_.has(E))throw new Error(`Unknown active Editor release: ${E}`);let m=e.logger??console,F=be(),ne=new Date().toISOString(),oe=e.testControl?e.controlToken?.trim()||Oe(32).toString("base64url"):null,V=e.runtimeUrl?.trim()||"http://127.0.0.1:3011",K=new URL(V).origin,Q=Oe(32).toString("base64url"),T=new Map,w=null,Ue=re(e.workerReadyTimeoutMs,3e4),X=re(e.workerHeartbeatTimeoutMs,1e4),ze=re(e.workerRestartLimit,3),Ge=re(e.workerRestartWindowMs,6e4),ge=at(e.workerWatchdogMode),ie=S.join(r,".fps-editor/state/editor-server.owner"),we=await ut(ie,F),I=null,Z=new WeakSet,ae=new WeakMap,z=!1,R="starting",A=0,G=null,W=null,se=0,x=[],ce=null,$=null,Y=Date.now(),Ie=null,D=null,N=!1,de,L=null,k=null,M=rt((t,n)=>{Be(t,n).catch(i=>{m.error?.(`[fps-editor-server] request failed: ${j(i)}`),n.headersSent?n.destroy(i instanceof Error?i:void 0):d(n,500,{ok:!1,error:j(i)})})});M.on("error",t=>m.error?.(`[fps-editor-server] ${j(t)}`));try{await new Promise((t,n)=>{M.once("error",n),M.listen(f,a,()=>{M.off("error",n),t()})}),await le().catch(t=>{m.error?.(`[fps-editor-server] initial Project I/O startup failed: ${j(t)}`)})}catch(t){throw await je(M).catch(()=>{}),await Pe(ie,we).catch(()=>{}),t}Y=Date.now(),L=setInterval(()=>{let t=Date.now(),n=t-Y;if(Y=t,n>X){Ie=n,D=t+X,N=!1,m.info?.(`[fps-editor-server] Supervisor resumed after a ${n}ms gap; granting Project I/O one heartbeat grace period.`);return}if(!(z||R!=="ready"||!$||!I)){if(t-Date.parse($)<=X){D=null,N=!1;return}if(!(D!==null&&t<=D)){if(D=null,ge==="observe"){N||(N=!0,m.warn?.("[fps-editor-server] Project I/O heartbeat timed out; observe mode left it running."));return}m.warn?.("[fps-editor-server] Project I/O heartbeat timed out; restarting worker."),N=!0,R="restarting",ae.set(I,"heartbeat-timeout"),I.kill("SIGKILL")}}},Math.min(1e3,Math.max(100,Math.floor(X/3)))),L.unref?.(),m.info?.(`[fps-editor-server] ready at ${p}`);function B(){return ye(Date.now()),Object.freeze({status:R,ready:R==="ready",endpointBase:P,generation:A,pid:I?.pid??null,serviceInstanceId:G,restartCount:se,recentFailureCount:x.length,watchdogMode:ge,lastSupervisorTickAt:new Date(Y).toISOString(),lastSuspendGapMs:Ie,lastRestartReason:ce,lastHeartbeatAt:$,...de?{lastExit:de}:{}})}function q(){let t=_.get(E),n=B();return{ok:!0,authority:"browser",serverInstanceId:F,pid:process.pid,startedAt:ne,projectEpoch:o,host:a,port:f,projectIO:n,worker:n,editor:{activeRelease:E,artifactId:t.artifactId,build:t.build}}}async function le(){if(z)return;A+=1,R=A===1?"starting":"restarting",G=null,W=null,$=null,D=null,N=!1;let t=nt(process.execPath,[O],{cwd:r,stdio:["ignore","inherit","inherit","ipc"],env:{...process.env,FPS_EDITOR_WORKER_GENERATION:String(A),FPS_EDITOR_WORKER_PROJECT_DEFINITION:JSON.stringify(h),FPS_EDITOR_WORKER_PLUGIN_SNAPSHOTS:JSON.stringify(y)}});I=t,t.on("message",c=>{We(t,c).catch(l=>{m.error?.(`[fps-editor-server] invalid Project I/O worker message: ${j(l)}`),t.kill("SIGKILL")})}),t.once("exit",(c,l)=>{Le(t,c,l)});let n=Date.now()+Ue,i=()=>R==="ready";for(;!z&&I===t&&!i()&&Date.now()<n;)await new Promise(c=>setTimeout(c,25));if(!i())throw I===t&&t.exitCode===null&&t.signalCode===null&&(R="restarting",ae.set(t,"worker-exit"),t.kill("SIGKILL")),new Error("Editor Project I/O worker did not become ready before the startup deadline.")}async function We(t,n){if(t!==I||!mt(n))return;if(n.type==="heartbeat"){n.serviceInstanceId===G&&($=new Date().toISOString());return}let i=new URL(n.internalUrl);if(i.protocol!=="http:"||i.hostname!=="127.0.0.1")throw new Error("Editor Project I/O worker internal URL must use loopback HTTP.");G=n.serviceInstanceId,W=i.toString(),$=new Date().toISOString(),D=null,N=!1,R="ready"}async function Le(t,n,i){let c=Z.has(t);Z.delete(t),I===t&&(I=null);let l=Date.now();if(de={code:n,signal:i,at:new Date(l).toISOString()},W=null,G=null,z||c){R="stopped";return}if(ce=ae.get(t)??"worker-exit",se+=1,x.push(l),ye(l),x.length>ze){R="fused",m.error?.("[fps-editor-server] Project I/O worker restart fuse opened.");return}R="restarting";let g=[100,500,2e3][Math.min(x.length-1,2)];k=setTimeout(()=>{k=null,le().catch(u=>{m.error?.(`[fps-editor-server] Project I/O worker restart failed: ${j(u)}`)})},g),k.unref?.()}function ye(t){let n=t-Ge;x=x.filter(i=>i>=n)}async function Be(t,n){let i=new URL(t.url??"/",p);if(pt(n),t.method==="GET"&&i.pathname==="/__fps_editor/health"){d(n,200,q());return}if(t.method==="GET"&&i.pathname==="/__fps_editor/status"){d(n,200,q());return}if(t.method==="GET"&&i.pathname==="/__fps_editor/project-io/health"){let c=B();d(n,c.ready?200:503,{ok:c.ready,projectIO:c});return}if(t.method==="GET"&&i.pathname===`${P}/health`){let c=B();d(n,200,{schemaVersion:1,kind:"project-io",ready:c.ready,status:c.ready?"ready":"unavailable",generation:c.generation,serviceInstanceId:c.serviceInstanceId,...c.ready?{}:{reason:c.status}});return}if(t.method==="GET"&&i.pathname==="/__fps_editor/bootstrap.json"){let c=_.get(E),l=B();d(n,200,{ok:!0,schemaVersion:1,authority:"browser",serverInstanceId:F,projectEpoch:o,workerGeneration:A,authoringEndpointBase:P,projectDefinition:h,pluginSnapshots:y,projectIO:{endpointBase:l.endpointBase,status:l.ready?"ready":"unavailable",generation:l.generation,serviceInstanceId:l.serviceInstanceId,...l.ready?{}:{reason:l.status}},runtimeUrl:V,playUrl:V,runtimeTuning:{endpointBase:H,token:Q,projectId:ue(h)},build:c.build});return}if(i.pathname.startsWith(`${H}/`)){await Ve(t,n,i);return}if((t.method==="GET"||t.method==="HEAD")&&(i.pathname==="/"||i.pathname==="/index.html"||i.pathname==="/__fps_editor/document")){await Je(n,i.searchParams.get("instanceId"),i.searchParams.get("release"),t.method==="HEAD");return}if((t.method==="GET"||t.method==="HEAD")&&i.pathname.startsWith("/__fps_editor/build/")){await Ke(t,n,i);return}if((t.method==="GET"||t.method==="HEAD")&&i.pathname.startsWith("/src/")){await Qe(t,n,i);return}if(t.method==="POST"&&i.pathname.startsWith("/__fps_editor/control/")){await Ze(t,n,i.pathname);return}if(i.pathname===P||i.pathname.startsWith(`${P}/`)){await Xe(t,n,i);return}d(n,404,{ok:!1,error:"not_found"})}async function Je(t,n,i,c=!1){let l=_.get(E);if(i?.trim()&&i.trim()!==E){d(t,409,{ok:!1,error:"editor_build_incompatible",activeRelease:E,requestedRelease:i.trim(),activeBuild:l.build});return}let g=await pe(S.join(l.rootPath,"index.html"),"utf8"),u=`/__fps_editor/build/${encodeURIComponent(l.artifactId)}/`,s=B(),v=[`<base href="${wt(u)}">`,"<script>","window.__FPS_EDITOR_HOSTED_SANDBOX__=true;",'window.__BOOT_MODE="edit";',`window.__FPS_EDITOR_SERVER__=${U({serverInstanceId:F,instanceId:n?.trim()||null,release:l.release,artifactId:l.artifactId,build:l.build})};`,`window.__FPS_EDITOR_PROJECT_IO__=${U({endpointBase:P,status:s.ready?"ready":"unavailable",generation:s.generation,serviceInstanceId:s.serviceInstanceId,...s.ready?{}:{reason:s.status}})};`,`window.__FPS_EDITOR_PROJECT_DEFINITION__=${U(h)};`,`window.__FPS_EDITOR_PLUGIN_SNAPSHOTS__=${U(y)};`,`window.__FPS_EDITOR_RUNTIME_URL__=${U(V)};`,`window.__FPS_EDITOR_RUNTIME_TUNING__=${U({endpointBase:H,token:Q,projectId:ue(h)})};`,"window.__FPS_EDITOR_PROJECT_IO_MONITOR__=setInterval(async()=>{",'try{const response=await fetch("/__fps_editor_authoring/health",{cache:"no-store"});','if(!response.ok)throw new Error("project-io-health-failed");const health=await response.json();const previous=window.__FPS_EDITOR_PROJECT_IO__;',`const next={endpointBase:"${P}",status:health.status,`,"generation:health.generation,serviceInstanceId:health.serviceInstanceId,reason:health.reason};","if(previous?.status===next.status&&previous?.generation===next.generation&&previous?.reason===next.reason)return;","window.__FPS_EDITOR_PROJECT_IO__=next;",'dispatchEvent(new CustomEvent("fps-editor:project-io-availability-changed",{detail:next}));',`}catch{const previous=window.__FPS_EDITOR_PROJECT_IO__;const next={endpointBase:"${P}",`,'status:"unavailable",generation:previous?.generation??0,serviceInstanceId:previous?.serviceInstanceId??null,','reason:"Project I/O health request failed."};',"if(previous?.status===next.status&&previous?.generation===next.generation&&previous?.reason===next.reason)return;","window.__FPS_EDITOR_PROJECT_IO__=next;",'dispatchEvent(new CustomEvent("fps-editor:project-io-availability-changed",{detail:next}));}',"},1000);",'addEventListener("pagehide",()=>clearInterval(window.__FPS_EDITOR_PROJECT_IO_MONITOR__),{once:true});',"</script>"].join(""),te=/<head[^>]*>/i.test(g)?g.replace(/<head[^>]*>/i,Ye=>`${Ye}${v}`):`${v}${g}`;t.statusCode=200,t.setHeader("Cache-Control","no-store"),t.setHeader("Content-Type","text/html; charset=utf-8"),t.end(c?void 0:te)}async function Ve(t,n,i){let c=i.pathname;if(t.method==="OPTIONS"){if(t.headers.origin!==K)return d(n,404,{ok:!1,error:"not_found"});n.statusCode=204,n.setHeader("Access-Control-Allow-Origin",K),n.setHeader("Access-Control-Allow-Methods","POST"),n.setHeader("Access-Control-Allow-Headers","content-type"),n.setHeader("Vary","Origin"),n.end();return}if(c===`${H}/consumer/poll`){let u=Ae(t);if(t.method!=="POST"||t.headers["x-fps-editor-runtime-tuning-token"]!==Q||!u)return d(n,404,{ok:!1,error:"not_found"});if(T.has(u)){d(n,409,{ok:!1,error:"runtime_tuning_consumer_busy"});return}let s={id:u,response:n,delivered:!1,timer:setTimeout(()=>{T.get(u)===s&&T.delete(u),d(n,204,{})},Se)};T.set(u,s),n.once("close",()=>{!s.delivered&&T.get(u)===s&&(clearTimeout(s.timer),T.delete(u))}),w&&T.size===1&&Ee(w);return}if(c===`${H}/consumer/result`){let u=Ae(t);if(t.method!=="POST"||t.headers["x-fps-editor-runtime-tuning-token"]!==Q||!u)return d(n,404,{ok:!1,error:"not_found"});let s=await Ce(t),v=typeof s.requestId=="string"?s.requestId:"";if(!w||v!==w.id||u!==w.consumerId)return d(n,409,{ok:!1,error:"runtime_tuning_stale_result"});let te=w;w=null,T.delete(u),clearTimeout(te.timer),te.settle(Ot(s.result)),d(n,200,{ok:!0});return}if(c!==`${H}/submit`||t.method!=="POST"||t.headers.origin!==K||!t.headers["content-type"]?.toLowerCase().startsWith("application/json"))return d(n,404,{ok:!1,error:"not_found"});n.setHeader("Access-Control-Allow-Origin",K),n.setHeader("Vary","Origin");let l;try{l=It(await Ce(t),ue(h))}catch(u){return d(n,400,{ok:!1,error:"runtime_tuning_invalid_intent",message:j(u).slice(0,240)})}if(T.size===0)return d(n,409,{ok:!1,error:"runtime_tuning_no_consumer"});if(T.size>1)return d(n,409,{ok:!1,error:"runtime_tuning_multiple_consumers",message:"\u68C0\u6D4B\u5230\u591A\u4E2A\u5F53\u524D\u9879\u76EE\u7F16\u8F91\u5668\u3002\u8BF7\u53EA\u4FDD\u7559\u4E00\u4E2A\u7F16\u8F91\u5668\u9875\u9762\u540E\u91CD\u8BD5\u3002"});if(w)return d(n,409,{ok:!1,error:"runtime_tuning_busy"});let g=await new Promise(u=>{let s={id:be(),intent:l,settle:u,consumerId:null,delivered:!1,timer:setTimeout(()=>{w===s&&(w=null),s.consumerId&&T.delete(s.consumerId),u({ok:!1,code:s.delivered?"runtime_tuning_outcome_unknown":"runtime_tuning_no_consumer",message:s.delivered?"\u7F16\u8F91\u5668\u5DF2\u6536\u5230\u8BF7\u6C42\uFF0C\u4F46\u7ED3\u679C\u5728\u9650\u5B9A\u65F6\u95F4\u5185\u672A\u77E5\u3002":"\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u7F16\u8F91\u5668\u3002"})},Se)};w=s,Ee(s)});d(n,g.ok===!0?200:409,g)}function Ee(t){if(T.size!==1)return;let n=T.values().next().value;!n||n.delivered||(clearTimeout(n.timer),n.delivered=!0,t.consumerId=n.id,t.delivered=!0,d(n.response,200,{ok:!0,requestId:t.id,intent:t.intent}))}async function Ke(t,n,i){let c=i.pathname.split("/").filter(Boolean),l=decodeURIComponent(c[2]??""),g=[..._.values()].find(s=>s.artifactId===l);if(!g){d(n,404,{ok:!1,error:"editor_build_not_found"});return}let u=c.slice(3).map(decodeURIComponent).join("/");await ve(t,n,g.rootPath,u,!0)}async function Qe(t,n,i){let c=decodeURIComponent(i.pathname.slice(1));await ve(t,n,r,c,!1,i.searchParams.has("v"))}async function Xe(t,n,i){if(!W||R!=="ready"){d(n,503,{ok:!1,error:"editor_project_io_unavailable",generation:A});return}let c;try{c=t.method==="GET"||t.method==="HEAD"?void 0:await Ne(t,i.pathname===`${P}/asset-import`?ot:128*1024*1024)}catch(s){if(s?.code!=="asset_import_payload_too_large")throw s;d(n,413,{ok:!1,error:"asset_import_payload_too_large",code:"asset_import_payload_too_large",details:s.details});return}let l={};for(let s of["accept","content-type","if-none-match","range","origin","access-control-request-method","access-control-request-headers"]){let v=t.headers[s];typeof v=="string"&&(l[s]=v)}let g;try{g=await fetch(new URL(`${i.pathname}${i.search}`,W),{method:t.method,headers:l,...c?{body:new Uint8Array(c)}:{}})}catch(s){m.warn?.(`[fps-editor-server] Project I/O request failed: ${j(s)}`),d(n,503,{ok:!1,error:"editor_project_io_unavailable",generation:A});return}n.statusCode=g.status;for(let s of["content-type","content-length","etag","content-range","accept-ranges","server-timing","access-control-allow-origin","access-control-allow-methods","access-control-allow-headers","access-control-expose-headers","vary"]){let v=g.headers.get(s);v&&n.setHeader(s,v)}let u=t.method==="HEAD"?null:Buffer.from(await g.arrayBuffer());n.setHeader("Cache-Control","no-store"),n.end(u??void 0)}async function Ze(t,n,i){if(!oe||t.headers["x-fps-editor-control-token"]!==oe){d(n,404,{ok:!1,error:"not_found"});return}if(i==="/__fps_editor/control/worker/crash"){if(!I){d(n,409,{ok:!1,error:"worker_unavailable"});return}I.kill("SIGKILL"),d(n,202,{ok:!0});return}if(i==="/__fps_editor/control/worker/heartbeat/stop"||i==="/__fps_editor/control/worker/heartbeat/start"){if(!I?.connected){d(n,409,{ok:!1,error:"worker_unavailable"});return}I.send({type:i.endsWith("/stop")?"test.stop-heartbeat":"test.start-heartbeat"}),d(n,200,{ok:!0});return}let c="/__fps_editor/control/activate/",l=decodeURIComponent(i.slice(c.length));if(i.startsWith(c)&&_.has(l)){let g=E;if(E=l,se=0,x=[],ce=null,k&&(clearTimeout(k),k=null),I){let u=I;R="restarting",Z.add(u);let s=Te(u,"exit");u.kill("SIGTERM"),await s}await le().catch(u=>{m.error?.(`[fps-editor-server] Project I/O activation restart failed: ${j(u)}`)}),d(n,200,{ok:!0,previousRelease:g,activeRelease:E,status:q()});return}d(n,404,{ok:!1,error:"control_not_found"})}let ee=null;return Object.freeze({url:p,controlToken:oe,status:q,dispose(){return ee||(ee=(async()=>{z=!0;for(let n of T.values())clearTimeout(n.timer),n.response.writableEnded||d(n.response,503,{ok:!1,code:"runtime_tuning_editor_unavailable",message:"\u7F16\u8F91\u5668\u670D\u52A1\u6B63\u5728\u5173\u95ED\u3002"});T.clear(),w&&(clearTimeout(w.timer),w.settle({ok:!1,code:w.delivered?"runtime_tuning_outcome_unknown":"runtime_tuning_editor_unavailable",message:w.delivered?"\u7F16\u8F91\u5668\u5DF2\u6536\u5230\u8BF7\u6C42\uFF0C\u4F46\u670D\u52A1\u5173\u95ED\u524D\u672A\u8FD4\u56DE\u53EF\u4FE1\u7ED3\u679C\u3002":"\u7F16\u8F91\u5668\u670D\u52A1\u6B63\u5728\u5173\u95ED\u3002"}),w=null),L&&clearInterval(L),k&&clearTimeout(k),L=null,k=null;let t=I;if(t){Z.add(t);let n=Te(t,"exit").catch(()=>[]);t.kill("SIGTERM"),await Promise.race([n,new Promise(i=>setTimeout(i,5e3))]),t.exitCode===null&&t.signalCode===null&&t.kill("SIGKILL")}await je(M),await Pe(ie,we)})(),ee)}})}function at(e){if(e===void 0||e==="restart")return"restart";if(e==="observe")return"observe";throw new Error(`Editor Project I/O watchdog mode must be restart or observe, got ${String(e)}.`)}function st(e){if(e===void 0)return Object.freeze({});if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Editor Server Plugin snapshots must be an object.");return Object.freeze(structuredClone(e))}function ct(e,r){let o=S.resolve(e,r.rootPath);if(Fe(e,o,"built Editor root"),!r.release.trim()||!r.artifactId.trim())throw new Error("Editor build identity is incomplete.");return Object.freeze({...r,rootPath:o,build:Object.freeze({...r.build})})}function dt(e){if(!e||typeof e!="object"||Array.isArray(e)||e.schemaVersion!==1||typeof e.projectId!="string"||typeof e.projectName!="string"||!e.authoring||!Array.isArray(e.plugins))throw new Error("Editor Server Project definition is invalid.");return structuredClone(e)}async function ve(e,r,o,a,f,p=!1){if(!a||a.includes("\\")||a.split("/").includes("..")){d(r,404,{ok:!1,error:"file_not_found"});return}let O=await fe(o).catch(()=>null),b=S.resolve(o,a),h=O?S.relative(O,b):"..";if(!O||!h||h===".."||h.startsWith(`..${S.sep}`)||S.isAbsolute(h)){d(r,404,{ok:!1,error:"file_not_found"});return}let y=await fe(b).catch(()=>null);if(!y||y!==b){d(r,404,{ok:!1,error:"file_not_found"});return}let _=await he(y).catch(()=>null);if(!_?.isFile()){d(r,404,{ok:!1,error:"file_not_found"});return}let E=e.headers["accept-encoding"]??"",m=await lt(y,E);r.statusCode=200,r.setHeader("Cache-Control",f||p?"public, max-age=31536000, immutable":"no-cache"),r.setHeader("Content-Type",ht(y)),r.setHeader("Content-Length",String(m?.stat.size??_.size)),m&&(r.setHeader("Content-Encoding",m.encoding),r.setHeader("Vary","Accept-Encoding")),e.method==="HEAD"?r.end():et(m?.path??y).pipe(r)}async function lt(e,r){for(let o of ke(r,"br")?[{suffix:".br",encoding:"br"},{suffix:".gz",encoding:"gzip"}]:[{suffix:".gz",encoding:"gzip"}]){if(o.encoding==="gzip"&&!ke(r,"gzip"))continue;let a=`${e}${o.suffix}`,f=await he(a).catch(()=>null);if(f?.isFile())return{path:a,encoding:o.encoding,stat:f}}return null}function ke(e,r){let o=typeof e=="string"?e:e.join(","),a=null;for(let f of o.toLowerCase().split(",")){let[p="",...O]=f.split(";").map(_=>_.trim());if(p!==r&&p!=="*")continue;let b=O.find(_=>_.startsWith("q=")),h=b?Number(b.slice(2)):1,y=Number.isFinite(h)?h:0;if(p===r)return y>0;a=y}return(a??0)>0}async function ut(e,r){await tt(S.dirname(e),{recursive:!0,mode:448});let o={pid:process.pid,serverInstanceId:r,createdAt:new Date().toISOString()};try{let a=await Re(e,"wx",384);return await a.writeFile(`${JSON.stringify(o)}
2
+ `,"utf8"),await a.close(),o}catch{let f=await pe(e,"utf8").then(JSON.parse).catch(()=>null);if(typeof f?.pid=="number"&&ft(f.pid))throw new Error(`Editor Server project lock is already owned by PID ${f.pid}.`);await xe(e,{force:!0});let p=await Re(e,"wx",384);return await p.writeFile(`${JSON.stringify(o)}
3
+ `,"utf8"),await p.close(),o}}async function Pe(e,r){(await pe(e,"utf8").then(JSON.parse).catch(()=>null))?.serverInstanceId===r.serverInstanceId&&await xe(e,{force:!0})}function ft(e){try{return process.kill(e,0),!0}catch{return!1}}function mt(e){if(!e||typeof e!="object")return!1;let r=e;return r.type==="heartbeat"?typeof r.serviceInstanceId=="string":r.type==="ready"&&typeof r.serviceInstanceId=="string"&&typeof r.internalUrl=="string"}async function Ne(e,r){let o=[],a=0;for await(let f of e){let p=Buffer.isBuffer(f)?f:Buffer.from(f);if(a+=p.byteLength,a>r)throw Object.assign(new Error("asset_import_payload_too_large"),{code:"asset_import_payload_too_large",details:{maxBytes:r}});o.push(p)}return Buffer.concat(o)}function pt(e){e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Access-Control-Expose-Headers","content-length, server-timing"),e.setHeader("Cross-Origin-Resource-Policy","cross-origin"),e.setHeader("Cross-Origin-Embedder-Policy","credentialless"),e.setHeader("X-Content-Type-Options","nosniff")}function d(e,r,o){e.statusCode=r,e.setHeader("Cache-Control","no-store"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.end(`${JSON.stringify(o)}
4
+ `)}function ht(e){switch(S.extname(e).toLowerCase()){case".js":case".mjs":return"text/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".html":return"text/html; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".glb":return"model/gltf-binary";case".gltf":return"model/gltf+json";default:return"application/octet-stream"}}function Fe(e,r,o){let a=S.relative(e,r);if(!a||a===".."||a.startsWith(`..${S.sep}`)||S.isAbsolute(a))throw new Error(`Editor Server ${o} must be inside the canonical project root.`)}function _t(e){if(!Number.isSafeInteger(e)||e<1||e>65535)throw new Error("Editor Server port is invalid.");return e}function re(e,r){return Number.isSafeInteger(e)&&e>0?e:r}function gt(e,r){return`http://${e==="0.0.0.0"?"127.0.0.1":e}:${r}`}function U(e){return JSON.stringify(e).replace(/</g,"\\u003c").replace(/>/g,"\\u003e")}function wt(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}async function je(e){e.listening&&await new Promise((r,o)=>{e.close(a=>a?o(a):r())})}function j(e){return e instanceof Error?e.message:String(e)}function ue(e){let r=e.projectId;return typeof r=="string"&&r.trim()?r.trim():"project"}async function Ce(e){let r=await Ne(e,it);if(!r||r.length===0)throw new Error("Runtime tuning body is required.");let o=JSON.parse(r.toString("utf8"));if(!o||typeof o!="object"||Array.isArray(o))throw new Error("Runtime tuning body must be a JSON object.");return o}function Ae(e){let r=e.headers["x-fps-editor-runtime-tuning-consumer"];return typeof r=="string"&&/^[a-zA-Z0-9_-]{8,96}$/u.test(r)?r:null}function It(e,r){if(!me(e,["projectId","kind","targets"]))throw new Error("Runtime tuning envelope contains unsupported fields.");if(e.projectId!==r)throw new Error("Runtime tuning project identity does not match.");let o=e.kind,a=e.targets;if(o!=="camera"&&o!=="light"||!Array.isArray(a)||a.length!==(o==="camera"?1:2))throw new Error("Runtime tuning intent is outside the fixed Camera/Light schema.");let f=o==="camera"?new Set(["camera"]):new Set(["environment-light","directional-light"]),p=new Set;for(let O of a){if(!O||typeof O!="object"||Array.isArray(O))throw new Error("Runtime tuning target is invalid.");let b=O;if(!me(b,["role","targetId","sourceId","changes"]))throw new Error("Runtime tuning target contains unsupported fields.");let h=typeof b.role=="string"?b.role:"";if(!f.has(h)||p.has(h))throw new Error("Runtime tuning target role is invalid.");p.add(h);let y=b.targetId,_=typeof y=="string"?y.trim():"",E=typeof b.sourceId=="string"?b.sourceId.trim():"",m=b.changes;if(!_||_.length>160||!E||E.length>160||!m||typeof m!="object"||Array.isArray(m)||Object.keys(m).length===0||Object.keys(m).length>24)throw new Error("Runtime tuning target is invalid.");for(let[F,ne]of Object.entries(m))if(!Et(h,F,ne))throw new Error("Runtime tuning intent contains an invalid field.")}if(p.size!==f.size)throw new Error("Runtime tuning target roles are incomplete.");return{projectId:r,kind:o,targets:a}}var yt=new Set(["camera.alpha","camera.beta","camera.radius","camera.orthoSize","camera.fov","camera.minZ","camera.maxZ","camera.inertia"]);function Et(e,r,o){if(e==="camera")return r==="camera.projection"?o==="orthographic"||o==="perspective":r==="camera.targetOffset"?J(o,["x","y","z"]):r==="camera.targetScreenOffset"?J(o,["x","y"]):yt.has(r)&&typeof o=="number"&&Number.isFinite(o);if(e==="environment-light")return r==="enabled"?typeof o=="boolean":r==="light.intensity"?typeof o=="number"&&Number.isFinite(o):r==="light.diffuseColor"||r==="light.groundColor"?J(o,["r","g","b"]):!1;if(e==="directional-light"){if(r==="enabled")return typeof o=="boolean";if(r==="light.intensity")return typeof o=="number"&&Number.isFinite(o);if(r==="light.diffuseColor")return J(o,["r","g","b"]);if(r==="transform.rotation")return J(o,["x","y","z"])}return!1}function J(e,r){if(!e||typeof e!="object"||Array.isArray(e))return!1;let o=e;return me(o,r)&&r.every(a=>typeof o[a]=="number"&&Number.isFinite(o[a]))}function me(e,r){let o=new Set(r);return Object.keys(e).every(a=>o.has(a))}function Ot(e){let r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=r.receipt&&typeof r.receipt=="object"&&!Array.isArray(r.receipt)?r.receipt:null;return{ok:r.ok===!0,...typeof r.code=="string"?{code:r.code.slice(0,96)}:{},...typeof r.message=="string"?{message:bt(r.message)}:{},...r.phase==="apply"||r.phase==="save"?{phase:r.phase}:{},...typeof r.draftApplied=="boolean"?{draftApplied:r.draftApplied}:{},...typeof r.dirty=="boolean"?{dirty:r.dirty}:{},...o?{receipt:Object.fromEntries(Object.entries(o).filter(([a,f])=>["version","updatedAt","sceneUrl"].includes(a)&&(typeof f=="string"||typeof f=="number")))}:{}}}function bt(e){return e.replace(/\/(?:Users|home|private|var|tmp)(?:\/[^\s"'<>]+)+/giu,"[redacted-path]").replace(/\b([a-z-]*(?:token|secret|authorization)[a-z-]*)\s*[:=]\s*[^\s,;]+/giu,"$1=[redacted]").replace(/\b[A-Z]:\\(?:[^\s"'<>]+\\)*[^\s"'<>]+/gu,"[redacted-path]").slice(0,320)}Rt();var He=Me.resolve(process.cwd()),C=JSON.parse(await Tt(Me.join(He,".fps-editor/current.json"),"utf8").catch(e=>{throw new Error("Editor deployment is not prepared. Run npm run prepare:server first.",{cause:e})}));if(C.schemaVersion!==4||typeof C.workerEntry!="string"||!C.projectDefinition||!Array.isArray(C.builds))throw new Error("Editor deployment state is invalid. Run npm run prepare:server again.");var _e=await De({projectRoot:He,workerEntry:C.workerEntry,projectDefinition:C.projectDefinition,pluginSnapshots:C.pluginSnapshots,builds:C.builds,activeRelease:process.env.FPS_EDITOR_RELEASE?.trim()||"a",host:process.env.FPS_EDITOR_SERVER_HOST?.trim()||"127.0.0.1",port:St(process.env.FPS_EDITOR_SERVER_PORT,3012),runtimeUrl:process.env.FPS_EDITOR_RUNTIME_URL?.trim()||"http://127.0.0.1:3011",workerReadyTimeoutMs:6e4,workerHeartbeatTimeoutMs:1e4,workerRestartLimit:3,workerRestartWindowMs:6e4,workerWatchdogMode:vt(process.env.FPS_EDITOR_WORKER_WATCHDOG),testControl:process.env.FPS_EDITOR_SERVER_TEST_CONTROL==="true",controlToken:process.env.FPS_EDITOR_SERVER_CONTROL_TOKEN});_e.controlToken&&console.log(`[fps-editor-server] test control token: ${_e.controlToken}`);var $e=!1;for(let e of["SIGINT","SIGTERM"])process.once(e,()=>{$e||($e=!0,_e.dispose().finally(()=>{process.exitCode=e==="SIGINT"?130:143}))});function Rt(){let e=Number.parseInt(process.versions.node.split(".")[0]??"",10);if(!Number.isSafeInteger(e)||e<24||e>=27)throw new Error(`fps-editor-server requires Node.js >=24 <27; current version is ${process.versions.node}.`)}function St(e,r){if(!e?.trim())return r;let o=Number(e);if(!Number.isSafeInteger(o)||o<1||o>65535)throw new Error(`FPS_EDITOR_SERVER_PORT must be a valid port, got ${e}.`);return o}function vt(e){if(!e?.trim()||e.trim()==="restart")return"restart";if(e.trim()==="observe")return"observe";throw new Error(`FPS_EDITOR_WORKER_WATCHDOG must be restart or observe, got ${e}.`)}