@vcp-dev/site-edit-bridge 0.10.30 → 0.10.32
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/README.md +8 -0
- package/dist/auto-bootstrap.d.ts +1 -0
- package/dist/auto-bootstrap.js +15 -15
- package/dist/client.d.ts +20 -0
- package/dist/client.js +1 -1
- package/dist/engine-worker.js +2 -2
- package/dist/index.js +10 -10
- package/dist/next-dev-server.js +4 -4
- package/dist/protocol.d.ts +17 -1
- package/dist/protocol.js +27 -0
- package/dist/server.js +2 -2
- package/dist/window-message.d.ts +2 -0
- package/dist/window-message.js +1 -1
- package/package.json +3 -3
package/dist/next-dev-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import Re from"node:http";import{existsSync as F,readFileSync as U}from"node:fs";import{randomUUID as Zt}from"node:crypto";import{createRequire as Le}from"node:module";import{Socket as We}from"node:net";import z from"node:path";import{basename as qe,dirname as J,join as G}from"node:path";import{fileURLToPath as ze}from"node:url";import{createRequire as Kt}from"node:module";import{pathToFileURL as Wt}from"node:url";import{Worker as $t}from"node:worker_threads";var be=["resolveComposition","initializeSession","getDocument","getHistoryStatus","getStyleContext","getObjectEditContext","getClassNameSource","getSourceTree","getSourceFile","resolveSourceLocation","execute","getOperationResult","undo","redo"];function ne(e){let t=new Error(e.message);return t.name=e.name,e.code!==void 0&&(t.code=e.code),t}import dt from"node:http";import q from"node:fs";import{createHash as ye,randomBytes as ht,timingSafeEqual as pt}from"node:crypto";import b from"node:path";import{WebSocket as Me,WebSocketServer as lt}from"ws";import{createPreviewSiteEditEngineRuntime as ft,openFileHistoryStore as gt,resolveManagedPageTemplateComposition as mt,scanSiteEditCompositionsFromFiles as yt}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as St,scanNextAppRouterRoutesFromFiles as It}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as wt}from"@vcp-dev/site-edit-engine/source-watcher";function _e(e){let t=new Map,n=[],i=new Map,s=[],o=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],r=new Map(o.map((d,f)=>[d,f])),c=e.pageComposition.status==="supported"?{status:"supported",containerKey:e.pageComposition.containerKey,hasBusinessMembers:e.pageComposition.hasBusinessMembers,placementUnitKeys:e.pageComposition.placementUnitKeys,placements:e.pageComposition.placements.map(d=>({placementKey:d.placementKey,placementUnitKey:d.placementUnitKey,relation:d.relation}))}:{status:"unsupported",reason:e.pageComposition.reason,diagnostics:e.pageComposition.diagnostics},p=e.entries.flatMap((d,f)=>d.dynamicDataSummary?[[f,d.dynamicDataSummary]]:[]),a=new Map(e.entries.map((d,f)=>[d.key,f])),l=[];for(let[d,f]of e.entries.entries()){let m=f.pageCompositionActionScope;if(!m)continue;if(m.kind!=="source-siblings"){l.push([d,{kind:m.kind}]);continue}let x=m.siblingKeys.map(w=>{let E=a.get(w);if(E===void 0)throw new Error("Render entry "+f.key+" references missing source sibling "+w);return E});l.push([d,{kind:"source-siblings",siblingEntryIndexes:x}])}let u=d=>{let f=t.get(d);if(typeof f=="number")return f;let m=n.length;return n.push(d),t.set(d,m),m},S=d=>{let f=null;for(let m of tt(d)){let x=`${f??"root"}\0${m}`,w=i.get(x);if(typeof w=="number"){f=w;continue}let E=s.length;s.push([f,u(m)]),i.set(x,E),f=E}if(f===null)throw new Error("Document key must not be empty");return f};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:c,tokens:n,nodes:s,entries:e.entries.map(d=>{let f=d.pageCompositionUnitKey?r.get(d.pageCompositionUnitKey)??-1:-1;if(d.pageCompositionUnitKey&&f<0)throw new Error(`Render entry ${d.key} references unknown page composition unit ${d.pageCompositionUnitKey}`);return[S(d.key),et(d.operationSummary),...f>=0?[f]:[]]}),...l.length>0?{pageCompositionActionScopes:l}:{},...e.entries.every(d=>d.parentKey!==void 0)?{entryParentNodeIds:e.entries.map(d=>d.parentKey===null?null:S(d.parentKey))}:{},...p.length>0?{dynamicDataSummaries:p}:{}}}function et(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function tt(e){if(!e)return[];let t=[],n=0,i=ve(e,n);if(i<0)return t.push(e),t;for(t.push(e.slice(0,i)),n=i;n<e.length;){if(i=ve(e,n+(e.startsWith("::",n)?2:1)),i<0){t.push(e.slice(n));break}t.push(e.slice(n,i)),n=i}return t}function ve(e,t){let n=e.indexOf("/",t),i=e.indexOf("::",t);return n<0?i:i<0?n:Math.min(n,i)}var ke="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",ue="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var C=class e{constructor(){}static parseMessage(t,n={}){let i=JSON.parse(t);if(!e.isMessage(i,n))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(t){return JSON.stringify(t)}static extractRequestInfo(t){if(y(t)&&y(t.payload)){let n=typeof t.payload.id=="string"?t.payload.id:"unknown",i=this.isRequestKind(t.payload.kind)?t.payload.kind:"get-document";return{id:n,kind:i}}return{id:"unknown",kind:"get-document"}}static isRequestKind(t){return t==="initialize-session"||t==="get-document"||t==="get-history-status"||t==="get-style-context"||t==="get-object-edit-context"||t==="get-class-name-source"||t==="get-source-tree"||t==="get-source-file"||t==="resolve-source-location"||t==="execute"||t==="get-operation-result"||t==="undo"||t==="redo"}static isMessage(t,n={}){return!y(t)||typeof t.type!="string"?!1:t.type==="request"?nt(t.payload,n):t.type==="response"||t.type==="error"?y(t.payload)&&typeof t.payload.id=="string":t.type==="event"?y(t.payload)&&y(t.payload.event):t.type==="auth"?y(t.payload)&&typeof t.payload.key=="string":t.type==="auth-ok"&&y(t.payload)&&t.payload.ok===!0}},Q=class{constructor(t){this.options=t,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new ce,this.eventDispatcher=new ae(n=>{this.sendJson(n)}),this.unsubscribe=t.engine.subscribe(n=>{this.handleEngineEvent(n)}),t.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(i){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:i instanceof Error?i.message:"Bridge queue overflow"}}),t.socket.close(1013,"bridge busy")}},t.socket.onclose=()=>{this.unsubscribe(),this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.clear()}}async handleMessage(t){try{let n=t;if(n.type!=="request")return;let i=n.payload,{id:s}=i;switch(i.kind){case"initialize-session":{let o=ie(i),r=await this.resolve(o),c=await this.options.engine.initializeSession({composition:r.context,...i.mutationContext?{mutationContext:i.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:N({...c,data:{...c.data,document:Pe(c.data.document)}},r.context)}});return}case"get-document":{let o=await this.resolveWireContext(i),r=await this.options.engine.getDocument(o);this.sendResult(s,i.kind,ct(r,o));return}case"get-history-status":{let o=await this.resolveWireContext(i),r=await this.options.engine.getHistoryStatus(o);this.sendResult(s,i.kind,N(r,o));return}case"get-style-context":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getStyleContext(o),o));return}case"get-object-edit-context":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getObjectEditContext(i.key,o,i.selectionRef),o));return}case"get-class-name-source":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getClassNameSource(i.key,o),o));return}case"get-source-tree":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getSourceTree(o),o));return}case"get-source-file":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getSourceFile({path:i.path,composition:o}),o));return}case"resolve-source-location":{let o=await this.resolveWireContext(i),r=N(await this.options.engine.resolveSourceLocation({key:i.key,composition:o}),o);this.sendResult(s,i.kind,r);return}case"execute":case"get-operation-result":{let o=await this.resolveOperationWireContext(i.request),r=await this.resolveWireContext(i);if(H(o)!==H(r))throw new O(ke,"The execute request composition does not match its operation composition.");let c=await this.normalizeOperationRequest(i.request,o);if(i.kind==="get-operation-result"){this.sendResult(s,i.kind,await this.options.engine.getOperationResult?.(c)??null);return}await this.handleBufferedOperation(s,i.kind,()=>this.options.engine.execute(c));return}case"undo":case"redo":{let o=await this.resolveWireContext(i);await this.handleBufferedOperation(s,i.kind,()=>i.kind==="undo"?this.options.engine.undo(o):this.options.engine.redo(o));return}}}catch(n){let i=C.extractRequestInfo(t);this.sendJson({type:"error",payload:{...i,message:re(n),code:oe(n)}})}}async resolve(t){return this.options.engine.resolveComposition(t)}async resolveAndAssert(t){let n=await this.resolve(t),i=this.session.activeComposition;if(!i)throw new O(ue,"Initialize a composition session before issuing this request.");if(H(i)!==H(n.context))throw new O(ke,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(t){let n=ie(t);return this.resolveAndAssert(n)}async resolveOperationWireContext(t){let n=rt(t);return this.resolveAndAssert(n)}activateSession(t){this.session.activeComposition=t,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(H(t))}async normalizeOperationRequest(t,n){let i={...t,composition:Te(n)};if(i.target.kind==="route"){let s=i.target,o=ie(s),r=await this.resolveAndAssert(o);i.target={...s,composition:Te(r)}}return i}handleEngineEvent(t){let n=t?.composition;if(!ot(n))return;let i=H(n);this.session.subscribedCompositionKeys.has(i)&&(this.eventDispatcher.push(t),t.type==="site-edit.graph.invalidated"&&i===at(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(i)))}sendResult(t,n,i){this.sendJson({type:"response",payload:{id:t,kind:n,result:i}})}async handleRawMessage(t){try{await this.handleMessage(C.parseMessage(t,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let i=ut(t);this.sendJson({type:"error",payload:{...i,message:re(n),code:oe(n)}})}}sendJson(t){this.options.socket.send(C.stringifyMessage(t))}async handleBufferedOperation(t,n,i){this.eventDispatcher.beginBuffering();try{let s=await i();this.sendResult(t,n,s)}catch(s){this.sendJson({type:"error",payload:{id:t,kind:n,message:re(s),code:oe(s)}})}finally{this.eventDispatcher.flush()}}},O=class extends Error{constructor(t,n){super(n),this.code=t,this.name="BridgeProtocolError"}},ae=class{constructor(t){this.send=t,this.bufferedEvents=null}push(t){let n={type:"event",payload:{event:t}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let t=this.bufferedEvents??[];this.bufferedEvents=null,t.forEach(n=>this.send(n))}},ce=class{constructor(t=64){this.maxQueued=t,this.queue=Promise.resolve(),this.queued=0}enqueue(t){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await t()},i=this.queue.then(n,n);return this.queue=i.then(()=>{},()=>{}),i}};function nt(e,t){if(!y(e)||typeof e.id!="string"||!C.isRequestKind(e.kind))return!1;let n=L(e.composition);switch(e.kind){case"initialize-session":return n&&(e.mutationContext===void 0||se(e.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof e.key=="string"&&n&&it(e.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof e.path=="string"&&n;case"execute":case"get-operation-result":return n&&st(e.request)&&(e.request.mutationContext===void 0||se(e.request.mutationContext))&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return n&&e.mutationContext===void 0||n&&se(e.mutationContext)}}function it(e){if(e===void 0)return!0;if(!y(e)||typeof e.key!="string"||e.key.length===0)return!1;let t=e.instanceIndex,n=e.instanceCount;return t!==void 0&&(typeof t!="number"||!Number.isInteger(t)||t<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof t=="number"&&typeof n=="number"&&t>=n?!1:Object.keys(e).every(i=>i==="key"||i==="instanceIndex"||i==="instanceCount")}function st(e){return y(e)&&typeof e.id=="string"&&typeof e.siteId=="string"&&typeof e.baseSnapshotId=="string"&&L(e.composition)&&typeof e.kind=="string"&&y(e.target)&&(e.target.kind!=="route"||L(e.target.composition))&&typeof e.documentVersion=="number"&&Number.isInteger(e.documentVersion)&&e.documentVersion>=0&&y(e.params)}function L(e){return y(e)&&typeof e.routeId=="string"&&e.routeId.length>0&&(e.templateId===void 0||typeof e.templateId=="string"&&e.templateId.length>0)&&Object.keys(e).every(t=>t==="routeId"||t==="templateId")}function ot(e){return y(e)&&typeof e.routeId=="string"&&(e.effectiveTemplateId===void 0||typeof e.effectiveTemplateId=="string")}function ie(e){if(L(e.composition))return e.composition;throw new O(ue,"A composition selector is required for this request.")}function rt(e){if(y(e)&&L(e.composition))return e.composition;throw new O(ue,"A composition selector is required for execute.")}function Te(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function at(e){return e.activeComposition?H(e.activeComposition):null}function H(e){return JSON.stringify([e.routeId,e.effectiveTemplateId??null])}function N(e,t){return y(e)&&e.ok===!0?{...e,composition:t}:e}function ct(e,t){return!y(e)||e.ok!==!0?e:{...e,...t?{composition:t}:{},data:Pe(e.data)}}function Pe(e){let t=e;return _e(t.routeId?t:{...t,routeId:t.composition.routeId})}function y(e){return typeof e=="object"&&e!==null}function se(e){return y(e)&&typeof e.editingSessionId=="string"&&e.editingSessionId.length>0&&typeof e.lockId=="string"&&e.lockId.length>0&&typeof e.ownerId=="string"&&e.ownerId.length>0&&typeof e.leaseAssertion=="string"&&e.leaseAssertion.length>0}function oe(e){if(!(!y(e)||typeof e.code!="string"||e.code.length===0))return e.code}function re(e){return e instanceof Error||y(e)&&typeof e.message=="string"&&e.message.length>0?e.message:"Unknown websocket operation failure"}function ut(e){try{return C.extractRequestInfo(JSON.parse(e))}catch{return{id:"unknown",kind:"get-document"}}}var Y=class{constructor(){this.tail=Promise.resolve()}run(t){let n=this.tail.then(t,t);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as Et,computeSiteEditCompositionKey as M}from"@vcp-dev/contracts";var xt="127.0.0.1",Rt=30218,Ct="/__vcp/site-edit/bridge",De=6*1024*1024,bt=De*2,vt=3e3,Ae=50,pe=".vcp/current-snapshot.json",Ne="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function Be(e,t,n={}){let i=new le(Ue(t),e,!1,n);return i.start({listen:!1}),i}var le=class{constructor(t,n=dt.createServer((o,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),i=!0,s={}){this.options=t,this.server=n,this.closeHttpServerOnStop=i,this.dependencies=s,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new Y,this.operationReceipts=new Map,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.launchCandidateEntries=null,this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.stopping=!1,this.runtimeVersionFloors=new Map,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve(),this.bridgeEngine=null,this.lastNotifiedHealth=null}start(t={}){if(!this.isStarted){this.stopping=!1;try{q.mkdirSync(b.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(n){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${n instanceof Error?n.message:String(n)}`)}if(this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime(),this.bridgeEngine=this.createBridgeEngine(),t.webSocket!==!1){let n=Se({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.bridgeEngine});this.stopWebSocketServer=n.stop}this.stopWatching=new wt({projectRoot:this.options.projectRoot,watch_dirs:_t(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??Ae,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),t.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.notifyHealthChanged()}}getEngine(){if(!this.bridgeEngine)throw new Error("VCP site edit preview bridge runtime is not ready");return this.bridgeEngine}getHealth(){let t=!!this.runtime&&!this.compositionResolutionError;return{ok:this.isStarted&&t,bridgePath:this.options.path,runtimeReady:t,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopping=!0,this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.closeHttpServerOnStop&&this.server.close();let t=At(),n=Nt(this.options);t.get(n)===this&&t.delete(n),this.isStarted=!1,this.notifyHealthChanged()}notifyHealthChanged(){let t=this.options.onHealthChanged;if(!t)return;let n=this.getHealth(),i=JSON.stringify(n);if(i!==this.lastNotifiedHealth){this.lastNotifiedHealth=i;try{t(n)}catch(s){console.warn(`[vcp-site-edit] onHealthChanged callback failed: ${s instanceof Error?s.message:String(s)}`)}}}createRuntime(t,n=null){let i=this.runtime;this.activeHistoryStore=t??null;let s=(this.dependencies.createRuntime??ft)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:o=>{"version"in o&&typeof o.version=="number"&&this.recordRuntimeVersion(o.version,"composition"in o?o.composition:void 0);for(let r of this.eventListeners)r(o)},...t?{historyStore:t}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=s,this.activeHistorySessionId=n,i?.dispose(),this.notifyHealthChanged()}createBridgeEngine(){return{resolveComposition:t=>this.resolveComposition(t),initializeSession:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)throw new Error(`${n.code}: ${n.message}`);let i=Tt(t.composition),s=this.getRuntime(),o=_(i);await s.initializeComposition(o);let c=s.getDocument(o).data;await this.recordRuntimeVersion(c.version,i);let p=(await s.getHistoryStatus()).data;return{ok:!0,composition:i,data:{document:c,historyStatus:p}}}),getDocument:async t=>{let n=this.getRuntime(),i=_(t);await n.initializeComposition(i);let s=n.getDocument(i);return await this.recordRuntimeVersion(s.data.version,t),{...s,composition:t}},getHistoryStatus:async t=>({...await this.getRuntime().getHistoryStatus(),composition:t}),getStyleContext:async t=>this.getRuntime().getStyleContext(_(t)),getObjectEditContext:async(t,n,i)=>this.getRuntime().getObjectEditContext(t,_(n),i),getClassNameSource:async(t,n)=>this.getRuntime().getClassNameSource(t,_(n)),getSourceTree:async t=>this.getRuntime().getSourceTree(_(t)),getSourceFile:async t=>this.getRuntime().getSourceFile({path:t.path,composition:_(t.composition)}),resolveSourceLocation:async t=>this.getRuntime().resolveSourceLocation({key:t.key,composition:_(t.composition)}),execute:(t,n)=>this.executeWithReceipt(t,()=>this.mutationQueue.run(async()=>{let i=await this.bindHistorySession(this.options.snapshotId);if(!i.ok)return de(i,t);let s=await this.getRuntime().executeOperation(t);return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition),s})),getOperationResult:t=>{if(t.siteId!==this.options.siteId||t.baseSnapshotId!==this.options.snapshotId)return null;let n=this.operationReceipts.get(this.operationReceiptKey(t));return n?.fingerprint===this.operationFingerprint(t)?n.result:null},undo:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return de(n,void 0,t);let i=await this.getRuntime().undo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??t),i.composition?i:{...i,composition:t}}),redo:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return de(n,void 0,t);let i=await this.getRuntime().redo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??t),i.composition?i:{...i,composition:t}}),subscribe:t=>(this.eventListeners.add(t),()=>this.eventListeners.delete(t))}}operationReceiptKey(t){return JSON.stringify([t.siteId,t.baseSnapshotId,t.id])}operationFingerprint(t){return ye("sha256").update(JSON.stringify(t)).digest("hex")}executeWithReceipt(t,n){if(t.siteId!==this.options.siteId||t.baseSnapshotId!==this.options.snapshotId)return n();let i=this.operationReceiptKey(t),s=this.operationFingerprint(t),o=this.operationReceipts.get(i);if(o)return o.fingerprint!==s?Promise.reject(new Error("Operation ID was reused with a different request.")):o.result;let r={fingerprint:s,result:n(),settled:!1};return this.operationReceipts.set(i,r),r.result.then(()=>{r.settled=!0;for(let[c,p]of this.operationReceipts){if(this.operationReceipts.size<=256)break;p.settled&&this.operationReceipts.delete(c)}},()=>{this.operationReceipts.delete(i)}),r.result}async bindHistorySession(t){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===t)return{ok:!0};let n=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===t)return;let i=await(this.dependencies.openHistoryStore??gt)(Pt({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:t}));if(!this.isStarted)return;let s=i.readDocumentVersion?await i.readDocumentVersion():0,o=this.compositions[0]?.context;if(o){let r=M(o);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,s))}this.createRuntime(i,t)});this.historySessionTransition=n.catch(()=>{});try{return await n,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(t,n){let i=n?M(n):null;i&&this.runtimeVersionFloors.set(i,Math.max(this.runtimeVersionFloors.get(i)??0,t));let s=this.activeHistoryStore;if(!s?.writeDocumentVersion)return Promise.resolve();let o=i?this.runtimeVersionFloors.get(i)??t:t;return this.versionPersistence=this.versionPersistence.then(()=>s.writeDocumentVersion(o)).catch(()=>{}),this.versionPersistence}handleFileChanged(t){this.handleFilesChanged([t])}handleFilesChanged(t){for(let i of t)this.pendingChangedFiles.add(i);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??Ae;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let t=[...this.pendingChangedFiles].sort((n,i)=>n.localeCompare(i));if(this.pendingChangedFiles.clear(),t.some(Ft)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let n of t)this.runtime?.notifyFileChanged(n)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let t=this.readCurrentSnapshotMarker();!t||t===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(t)).catch(n=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${n instanceof Error?n.message:String(n)}`)})}readCurrentSnapshotMarker(){try{let t=q.readFileSync(b.join(this.options.projectRoot,pe),"utf8"),n=JSON.parse(t),i=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!i||fe(i)!==i?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):i}catch(t){return console.warn(`[vcp-site-edit] current-snapshot marker unreadable: ${t instanceof Error?t.message:String(t)}`),null}}async advanceSnapshotId(t){if(this.stopping||!this.isStarted||t===this.options.snapshotId)return;let n=this.options.snapshotId;this.options.snapshotId=t;try{if(this.options.historyRoot){let s=await this.bindHistorySession(t);if(!s.ok)throw new Error(`${s.code}: ${s.message}`)}else this.createRuntime()}catch(s){this.options.snapshotId=n,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${n}: ${s instanceof Error?s.message:String(s)}`);return}let i=new Date().toISOString();for(let s of this.compositions){let o={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:s.context,snapshotId:t,previousSnapshotId:n,at:i};for(let r of this.eventListeners)try{r(o)}catch(c){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${c instanceof Error?c.message:String(c)}`)}}try{this.options.onSnapshotIdChanged?.(t,n)}catch(s){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${s instanceof Error?s.message:String(s)}`)}}refreshCompositions(t=!1){try{let n=kt(this.options.projectRoot),i=It(n),s=i.length>0?i:(this.options.runtime.routes??[]).map(a=>({...a})),o=yt({files:n});if(o.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=JSON.stringify(o.filter(a=>a.context.effectiveTemplateId!==void 0).map(a=>a.entryFile).sort());if(t&&(this.launchCandidateEntries=r),this.launchCandidateEntries!==r)throw new Error("SITE_EDIT_RUNTIME_RELOAD_REQUIRED: Candidate route collection changed since preview launch");let c=new Map(this.compositions.map(a=>[M(a.context),a])),p=new Map(o.map(a=>[M(a.context),a]));if(this.runtime){for(let[a,l]of c){let u=p.get(a);(!u||u.entryFile!==l.entryFile)&&this.runtime.unregisterComposition(_(l.context))}for(let[a,l]of p){let u=c.get(a);(!u||u.entryFile!==l.entryFile)&&this.runtime.registerComposition(l.context,l.entryFile)}}return this.routes=s,this.compositions=o,this.compositionFiles=n,this.compositionResolutionError=null,this.notifyHealthChanged(),!0}catch(n){let i=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=i,t)throw i;return this.notifyHealthChanged(),!1}}resolveComposition(t){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=St(t.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let i=this.routes.find(r=>r.routeId===n);if(!i)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let s=mt({selector:{routeId:n,...t.templateId!==void 0?{templateId:t.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:i.entryFile}),o=M(s.context);if(!this.compositions.some(r=>M(r.context)===o))throw new Error(`Composition is not registered by the preview bridge: ${o}`);return s}getInitialDocumentVersions(){return this.compositions.flatMap(t=>{let n=this.runtimeVersionFloors.get(M(t.context));return n===void 0?[]:[{composition:t.context,version:n}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function je(e){return Ue(e)}function Ue(e){return{tenantId:e.tenantId,siteId:e.siteId,snapshotId:e.snapshotId,projectRoot:b.resolve(e.projectRoot??process.cwd()),basePath:e.basePath??"",host:e.host??xt,port:e.port??Rt,path:e.path??Ct,sessionKey:e.sessionKey??Ut(),historyRoot:e.historyRoot,allowedOrigins:e.allowedOrigins??[],runtime:e.runtime??{},onSnapshotIdChanged:e.onSnapshotIdChanged,onHealthChanged:e.onHealthChanged}}function _t(e){let t=["app","src/app","components/site","src/components/site","lib/generated","src/lib/generated","public"];return[...new Set([...e?.length?e:t,".vcp"])]}function kt(e){let t=[],n=["app","src/app",".vcp"],i=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),s=o=>{if(!q.existsSync(o))return;let r;try{r=q.readdirSync(o,{withFileTypes:!0})}catch{return}for(let c of r){if(c.name.startsWith(".")&&c.name!==".vcp")continue;if(c.isDirectory()){if(i.has(c.name))continue;s(b.join(o,c.name));continue}if(!c.isFile())continue;let p=b.join(o,c.name),a="";try{a=q.readFileSync(p,"utf8")}catch{}t.push({path:b.relative(e,p).replaceAll(b.sep,"/"),content:a})}};for(let o of n)s(b.join(e,o));return t.sort((o,r)=>o.path.localeCompare(r.path))}function Tt(e){if(!e||typeof e!="object"||typeof e.routeId!="string")throw new Error("Normalized composition context is required.");if("effectiveTemplateId"in e)return e;let t=e;return{routeId:t.routeId,...t.templateId?{effectiveTemplateId:t.templateId}:{}}}function _(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function Pt(e){return b.join(b.resolve(e.root),fe(e.siteId),fe(e.snapshotId),ye("sha256").update(e.editingSessionId).digest("hex").slice(0,32))}function fe(e){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e)&&e!=="."&&e!==".."?e:ye("sha256").update(e).digest("hex").slice(0,32)}function At(){let e=globalThis;return e[Ne]??=new Map,e[Ne]}function Nt(e){return`${e.projectRoot}:${e.host}:${e.port}:${e.path}:${e.historyRoot??""}`}function de(e,t,n){let i=n??(t?.composition?{routeId:t.composition.routeId,...t.composition.templateId?{effectiveTemplateId:t.composition.templateId}:{}}:void 0);if(!i)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:i,error:{code:e.code,message:e.message},data:{requestId:t?.id??"mutation-context",kind:t?.kind??"update-text",composition:i,target:t?.target??{kind:"node",key:""},resultVersion:t?.documentVersion??0}}}function Se(e){let t=new lt({maxPayload:De,noServer:!0}),n=new WeakSet,i=o=>{new Q({socket:new ge(o),engine:e.engine,validateOperationRequest:r=>Et.safeParse(r).success})},s=(o,r,c)=>{if(!Ht(o,e))return;let p=Ot(o,e);if(!p){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import Re from"node:http";import{existsSync as F,readFileSync as U}from"node:fs";import{randomUUID as tn}from"node:crypto";import{createRequire as Le}from"node:module";import{Socket as We}from"node:net";import z from"node:path";import{basename as qe,dirname as J,join as G}from"node:path";import{fileURLToPath as ze}from"node:url";import{createRequire as $t}from"node:module";import{pathToFileURL as Vt}from"node:url";import{Worker as Lt}from"node:worker_threads";var be=["resolveComposition","initializeSession","getDocument","getHistoryStatus","getStyleContext","getObjectEditContext","getClassNameSource","getSourceTree","getSourceFile","resolveSourceLocation","execute","getOperationResult","undo","redo"];function ne(e){let t=new Error(e.message);return t.name=e.name,e.code!==void 0&&(t.code=e.code),t}import ht from"node:http";import q from"node:fs";import{createHash as ye,randomBytes as lt,timingSafeEqual as ft}from"node:crypto";import b from"node:path";import{WebSocket as Me,WebSocketServer as gt}from"ws";import{createPreviewSiteEditEngineRuntime as mt,openFileHistoryStore as yt,resolveManagedPageTemplateComposition as St,scanSiteEditCompositionsFromFiles as It}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as Et,scanNextAppRouterRoutesFromFiles as wt}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as xt}from"@vcp-dev/site-edit-engine/source-watcher";function _e(e){let t=new Map,n=[],i=new Map,s=[],o=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],r=new Map(o.map((d,f)=>[d,f])),c=e.pageComposition.status==="supported"?{status:"supported",containerKey:e.pageComposition.containerKey,hasBusinessMembers:e.pageComposition.hasBusinessMembers,placementUnitKeys:e.pageComposition.placementUnitKeys,placements:e.pageComposition.placements.map(d=>({placementKey:d.placementKey,placementUnitKey:d.placementUnitKey,relation:d.relation}))}:{status:"unsupported",reason:e.pageComposition.reason,diagnostics:e.pageComposition.diagnostics},h=e.entries.flatMap((d,f)=>d.dynamicDataSummary?[[f,d.dynamicDataSummary]]:[]),a=new Map(e.entries.map((d,f)=>[d.key,f])),l=[];for(let[d,f]of e.entries.entries()){let y=f.pageCompositionActionScope;if(!y)continue;if(y.kind!=="source-siblings"){l.push([d,{kind:y.kind}]);continue}let x=y.siblingKeys.map(E=>{let w=a.get(E);if(w===void 0)throw new Error("Render entry "+f.key+" references missing source sibling "+E);return w});l.push([d,{kind:"source-siblings",siblingEntryIndexes:x}])}let u=d=>{let f=t.get(d);if(typeof f=="number")return f;let y=n.length;return n.push(d),t.set(d,y),y},S=d=>{let f=null;for(let y of tt(d)){let x=`${f??"root"}\0${y}`,E=i.get(x);if(typeof E=="number"){f=E;continue}let w=s.length;s.push([f,u(y)]),i.set(x,w),f=w}if(f===null)throw new Error("Document key must not be empty");return f};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:c,tokens:n,nodes:s,entries:e.entries.map(d=>{let f=d.pageCompositionUnitKey?r.get(d.pageCompositionUnitKey)??-1:-1;if(d.pageCompositionUnitKey&&f<0)throw new Error(`Render entry ${d.key} references unknown page composition unit ${d.pageCompositionUnitKey}`);return[S(d.key),et(d.operationSummary),...f>=0?[f]:[]]}),...l.length>0?{pageCompositionActionScopes:l}:{},...e.entries.every(d=>d.parentKey!==void 0)?{entryParentNodeIds:e.entries.map(d=>d.parentKey===null?null:S(d.parentKey))}:{},...h.length>0?{dynamicDataSummaries:h}:{}}}function et(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function tt(e){if(!e)return[];let t=[],n=0,i=ve(e,n);if(i<0)return t.push(e),t;for(t.push(e.slice(0,i)),n=i;n<e.length;){if(i=ve(e,n+(e.startsWith("::",n)?2:1)),i<0){t.push(e.slice(n));break}t.push(e.slice(n,i)),n=i}return t}function ve(e,t){let n=e.indexOf("/",t),i=e.indexOf("::",t);return n<0?i:i<0?n:Math.min(n,i)}var ke="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",ue="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var C=class e{constructor(){}static parseMessage(t,n={}){let i=JSON.parse(t);if(!e.isMessage(i,n))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(t){return JSON.stringify(t)}static extractRequestInfo(t){if(m(t)&&m(t.payload)){let n=typeof t.payload.id=="string"?t.payload.id:"unknown",i=this.isRequestKind(t.payload.kind)?t.payload.kind:"get-document";return{id:n,kind:i}}return{id:"unknown",kind:"get-document"}}static isRequestKind(t){return t==="initialize-session"||t==="get-document"||t==="get-history-status"||t==="get-style-context"||t==="get-object-edit-context"||t==="get-class-name-source"||t==="get-source-tree"||t==="get-source-file"||t==="resolve-source-location"||t==="execute"||t==="get-operation-result"||t==="undo"||t==="redo"}static isMessage(t,n={}){return!m(t)||typeof t.type!="string"?!1:t.type==="request"?st(t.payload,n):t.type==="response"||t.type==="error"?m(t.payload)&&typeof t.payload.id=="string":t.type==="event"?m(t.payload)&&m(t.payload.event):t.type==="auth"?m(t.payload)&&typeof t.payload.key=="string":t.type==="ping"||t.type==="pong"?m(t.payload)&&typeof t.payload.id=="string":t.type==="auth-ok"&&m(t.payload)&&t.payload.ok===!0}},Q=class{constructor(t){this.options=t,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new ce,this.eventDispatcher=new ae(n=>{this.sendJson(n)}),this.unsubscribe=t.engine.subscribe(n=>{this.handleEngineEvent(n)}),t.socket.onmessage=n=>{let i=it(n.data);if(i!==null){this.sendJson({type:"pong",payload:{id:i}});return}try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(s){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:s instanceof Error?s.message:"Bridge queue overflow"}}),t.socket.close(1013,"bridge busy")}},t.socket.onclose=()=>{this.unsubscribe(),this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.clear()}}async handleMessage(t){try{let n=t;if(n.type!=="request")return;let i=n.payload,{id:s}=i;switch(i.kind){case"initialize-session":{let o=ie(i),r=await this.resolve(o),c=await this.options.engine.initializeSession({composition:r.context,...i.mutationContext?{mutationContext:i.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:s,kind:i.kind,result:N({...c,data:{...c.data,document:Pe(c.data.document)}},r.context)}});return}case"get-document":{let o=await this.resolveWireContext(i),r=await this.options.engine.getDocument(o);this.sendResult(s,i.kind,dt(r,o));return}case"get-history-status":{let o=await this.resolveWireContext(i),r=await this.options.engine.getHistoryStatus(o);this.sendResult(s,i.kind,N(r,o));return}case"get-style-context":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getStyleContext(o),o));return}case"get-object-edit-context":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getObjectEditContext(i.key,o,i.selectionRef),o));return}case"get-class-name-source":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getClassNameSource(i.key,o),o));return}case"get-source-tree":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getSourceTree(o),o));return}case"get-source-file":{let o=await this.resolveWireContext(i);this.sendResult(s,i.kind,N(await this.options.engine.getSourceFile({path:i.path,composition:o}),o));return}case"resolve-source-location":{let o=await this.resolveWireContext(i),r=N(await this.options.engine.resolveSourceLocation({key:i.key,composition:o}),o);this.sendResult(s,i.kind,r);return}case"execute":case"get-operation-result":{let o=await this.resolveOperationWireContext(i.request),r=await this.resolveWireContext(i);if(H(o)!==H(r))throw new O(ke,"The execute request composition does not match its operation composition.");let c=await this.normalizeOperationRequest(i.request,o);if(i.kind==="get-operation-result"){this.sendResult(s,i.kind,await this.options.engine.getOperationResult?.(c)??null);return}await this.handleBufferedOperation(s,i.kind,()=>this.options.engine.execute(c));return}case"undo":case"redo":{let o=await this.resolveWireContext(i);await this.handleBufferedOperation(s,i.kind,()=>i.kind==="undo"?this.options.engine.undo(o):this.options.engine.redo(o));return}}}catch(n){let i=C.extractRequestInfo(t);this.sendJson({type:"error",payload:{...i,message:re(n),code:oe(n)}})}}async resolve(t){return this.options.engine.resolveComposition(t)}async resolveAndAssert(t){let n=await this.resolve(t),i=this.session.activeComposition;if(!i)throw new O(ue,"Initialize a composition session before issuing this request.");if(H(i)!==H(n.context))throw new O(ke,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(t){let n=ie(t);return this.resolveAndAssert(n)}async resolveOperationWireContext(t){let n=ct(t);return this.resolveAndAssert(n)}activateSession(t){this.session.activeComposition=t,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(H(t))}async normalizeOperationRequest(t,n){let i={...t,composition:Te(n)};if(i.target.kind==="route"){let s=i.target,o=ie(s),r=await this.resolveAndAssert(o);i.target={...s,composition:Te(r)}}return i}handleEngineEvent(t){let n=t?.composition;if(!at(n))return;let i=H(n);this.session.subscribedCompositionKeys.has(i)&&(this.eventDispatcher.push(t),t.type==="site-edit.graph.invalidated"&&i===ut(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(i)))}sendResult(t,n,i){this.sendJson({type:"response",payload:{id:t,kind:n,result:i}})}async handleRawMessage(t){try{await this.handleMessage(C.parseMessage(t,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let i=pt(t);this.sendJson({type:"error",payload:{...i,message:re(n),code:oe(n)}})}}sendJson(t){this.options.socket.send(C.stringifyMessage(t))}async handleBufferedOperation(t,n,i){this.eventDispatcher.beginBuffering();try{let s=await i();this.sendResult(t,n,s)}catch(s){this.sendJson({type:"error",payload:{id:t,kind:n,message:re(s),code:oe(s)}})}finally{this.eventDispatcher.flush()}}},nt=256;function it(e){if(e.length>nt||!e.includes('"ping"'))return null;try{let t=JSON.parse(e);return m(t)&&t.type==="ping"&&m(t.payload)&&typeof t.payload.id=="string"?t.payload.id:null}catch{return null}}var O=class extends Error{constructor(t,n){super(n),this.code=t,this.name="BridgeProtocolError"}},ae=class{constructor(t){this.send=t,this.bufferedEvents=null}push(t){let n={type:"event",payload:{event:t}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let t=this.bufferedEvents??[];this.bufferedEvents=null,t.forEach(n=>this.send(n))}},ce=class{constructor(t=64){this.maxQueued=t,this.queue=Promise.resolve(),this.queued=0}enqueue(t){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await t()},i=this.queue.then(n,n);return this.queue=i.then(()=>{},()=>{}),i}};function st(e,t){if(!m(e)||typeof e.id!="string"||!C.isRequestKind(e.kind))return!1;let n=L(e.composition);switch(e.kind){case"initialize-session":return n&&(e.mutationContext===void 0||se(e.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof e.key=="string"&&n&&ot(e.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof e.path=="string"&&n;case"execute":case"get-operation-result":return n&&rt(e.request)&&(e.request.mutationContext===void 0||se(e.request.mutationContext))&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return n&&e.mutationContext===void 0||n&&se(e.mutationContext)}}function ot(e){if(e===void 0)return!0;if(!m(e)||typeof e.key!="string"||e.key.length===0)return!1;let t=e.instanceIndex,n=e.instanceCount;return t!==void 0&&(typeof t!="number"||!Number.isInteger(t)||t<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof t=="number"&&typeof n=="number"&&t>=n?!1:Object.keys(e).every(i=>i==="key"||i==="instanceIndex"||i==="instanceCount")}function rt(e){return m(e)&&typeof e.id=="string"&&typeof e.siteId=="string"&&typeof e.baseSnapshotId=="string"&&L(e.composition)&&typeof e.kind=="string"&&m(e.target)&&(e.target.kind!=="route"||L(e.target.composition))&&typeof e.documentVersion=="number"&&Number.isInteger(e.documentVersion)&&e.documentVersion>=0&&m(e.params)}function L(e){return m(e)&&typeof e.routeId=="string"&&e.routeId.length>0&&(e.templateId===void 0||typeof e.templateId=="string"&&e.templateId.length>0)&&Object.keys(e).every(t=>t==="routeId"||t==="templateId")}function at(e){return m(e)&&typeof e.routeId=="string"&&(e.effectiveTemplateId===void 0||typeof e.effectiveTemplateId=="string")}function ie(e){if(L(e.composition))return e.composition;throw new O(ue,"A composition selector is required for this request.")}function ct(e){if(m(e)&&L(e.composition))return e.composition;throw new O(ue,"A composition selector is required for execute.")}function Te(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function ut(e){return e.activeComposition?H(e.activeComposition):null}function H(e){return JSON.stringify([e.routeId,e.effectiveTemplateId??null])}function N(e,t){return m(e)&&e.ok===!0?{...e,composition:t}:e}function dt(e,t){return!m(e)||e.ok!==!0?e:{...e,...t?{composition:t}:{},data:Pe(e.data)}}function Pe(e){let t=e;return _e(t.routeId?t:{...t,routeId:t.composition.routeId})}function m(e){return typeof e=="object"&&e!==null}function se(e){return m(e)&&typeof e.editingSessionId=="string"&&e.editingSessionId.length>0&&typeof e.lockId=="string"&&e.lockId.length>0&&typeof e.ownerId=="string"&&e.ownerId.length>0&&typeof e.leaseAssertion=="string"&&e.leaseAssertion.length>0}function oe(e){if(!(!m(e)||typeof e.code!="string"||e.code.length===0))return e.code}function re(e){return e instanceof Error||m(e)&&typeof e.message=="string"&&e.message.length>0?e.message:"Unknown websocket operation failure"}function pt(e){try{return C.extractRequestInfo(JSON.parse(e))}catch{return{id:"unknown",kind:"get-document"}}}var X=class{constructor(){this.tail=Promise.resolve()}run(t){let n=this.tail.then(t,t);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as Rt,computeSiteEditCompositionKey as M}from"@vcp-dev/contracts";var Ct="127.0.0.1",bt=30218,vt="/__vcp/site-edit/bridge",De=6*1024*1024,_t=De*2,kt=3e3,Ae=50,he=".vcp/current-snapshot.json",Ne="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function Be(e,t,n={}){let i=new le(Ue(t),e,!1,n);return i.start({listen:!1}),i}var le=class{constructor(t,n=ht.createServer((o,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),i=!0,s={}){this.options=t,this.server=n,this.closeHttpServerOnStop=i,this.dependencies=s,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new X,this.operationReceipts=new Map,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.launchCandidateEntries=null,this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.stopping=!1,this.runtimeVersionFloors=new Map,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve(),this.bridgeEngine=null,this.lastNotifiedHealth=null}start(t={}){if(!this.isStarted){this.stopping=!1;try{q.mkdirSync(b.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(n){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${n instanceof Error?n.message:String(n)}`)}if(this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime(),this.bridgeEngine=this.createBridgeEngine(),t.webSocket!==!1){let n=Se({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.bridgeEngine});this.stopWebSocketServer=n.stop}this.stopWatching=new xt({projectRoot:this.options.projectRoot,watch_dirs:Tt(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??Ae,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),t.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.notifyHealthChanged()}}getEngine(){if(!this.bridgeEngine)throw new Error("VCP site edit preview bridge runtime is not ready");return this.bridgeEngine}getHealth(){let t=!!this.runtime&&!this.compositionResolutionError;return{ok:this.isStarted&&t,bridgePath:this.options.path,runtimeReady:t,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopping=!0,this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.closeHttpServerOnStop&&this.server.close();let t=Ht(),n=Ot(this.options);t.get(n)===this&&t.delete(n),this.isStarted=!1,this.notifyHealthChanged()}notifyHealthChanged(){let t=this.options.onHealthChanged;if(!t)return;let n=this.getHealth(),i=JSON.stringify(n);if(i!==this.lastNotifiedHealth){this.lastNotifiedHealth=i;try{t(n)}catch(s){console.warn(`[vcp-site-edit] onHealthChanged callback failed: ${s instanceof Error?s.message:String(s)}`)}}}createRuntime(t,n=null){let i=this.runtime;this.activeHistoryStore=t??null;let s=(this.dependencies.createRuntime??mt)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:o=>{"version"in o&&typeof o.version=="number"&&this.recordRuntimeVersion(o.version,"composition"in o?o.composition:void 0);for(let r of this.eventListeners)r(o)},...t?{historyStore:t}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=s,this.activeHistorySessionId=n,i?.dispose(),this.notifyHealthChanged()}createBridgeEngine(){return{resolveComposition:t=>this.resolveComposition(t),initializeSession:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)throw new Error(`${n.code}: ${n.message}`);let i=At(t.composition),s=this.getRuntime(),o=_(i);await s.initializeComposition(o);let c=s.getDocument(o).data;await this.recordRuntimeVersion(c.version,i);let h=(await s.getHistoryStatus()).data;return{ok:!0,composition:i,data:{document:c,historyStatus:h}}}),getDocument:async t=>{let n=this.getRuntime(),i=_(t);await n.initializeComposition(i);let s=n.getDocument(i);return await this.recordRuntimeVersion(s.data.version,t),{...s,composition:t}},getHistoryStatus:async t=>({...await this.getRuntime().getHistoryStatus(),composition:t}),getStyleContext:async t=>this.getRuntime().getStyleContext(_(t)),getObjectEditContext:async(t,n,i)=>this.getRuntime().getObjectEditContext(t,_(n),i),getClassNameSource:async(t,n)=>this.getRuntime().getClassNameSource(t,_(n)),getSourceTree:async t=>this.getRuntime().getSourceTree(_(t)),getSourceFile:async t=>this.getRuntime().getSourceFile({path:t.path,composition:_(t.composition)}),resolveSourceLocation:async t=>this.getRuntime().resolveSourceLocation({key:t.key,composition:_(t.composition)}),execute:(t,n)=>this.executeWithReceipt(t,()=>this.mutationQueue.run(async()=>{let i=await this.bindHistorySession(this.options.snapshotId);if(!i.ok)return de(i,t);let s=await this.getRuntime().executeOperation(t);return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition),s})),getOperationResult:t=>{if(t.siteId!==this.options.siteId||t.baseSnapshotId!==this.options.snapshotId)return null;let n=this.operationReceipts.get(this.operationReceiptKey(t));return n?.fingerprint===this.operationFingerprint(t)?n.result:null},undo:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return de(n,void 0,t);let i=await this.getRuntime().undo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??t),i.composition?i:{...i,composition:t}}),redo:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return de(n,void 0,t);let i=await this.getRuntime().redo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??t),i.composition?i:{...i,composition:t}}),subscribe:t=>(this.eventListeners.add(t),()=>this.eventListeners.delete(t))}}operationReceiptKey(t){return JSON.stringify([t.siteId,t.baseSnapshotId,t.id])}operationFingerprint(t){return ye("sha256").update(JSON.stringify(t)).digest("hex")}executeWithReceipt(t,n){if(t.siteId!==this.options.siteId||t.baseSnapshotId!==this.options.snapshotId)return n();let i=this.operationReceiptKey(t),s=this.operationFingerprint(t),o=this.operationReceipts.get(i);if(o)return o.fingerprint!==s?Promise.reject(new Error("Operation ID was reused with a different request.")):o.result;let r={fingerprint:s,result:n(),settled:!1};return this.operationReceipts.set(i,r),r.result.then(()=>{r.settled=!0;for(let[c,h]of this.operationReceipts){if(this.operationReceipts.size<=256)break;h.settled&&this.operationReceipts.delete(c)}},()=>{this.operationReceipts.delete(i)}),r.result}async bindHistorySession(t){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===t)return{ok:!0};let n=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===t)return;let i=await(this.dependencies.openHistoryStore??yt)(Nt({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:t}));if(!this.isStarted)return;let s=i.readDocumentVersion?await i.readDocumentVersion():0,o=this.compositions[0]?.context;if(o){let r=M(o);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,s))}this.createRuntime(i,t)});this.historySessionTransition=n.catch(()=>{});try{return await n,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(t,n){let i=n?M(n):null;i&&this.runtimeVersionFloors.set(i,Math.max(this.runtimeVersionFloors.get(i)??0,t));let s=this.activeHistoryStore;if(!s?.writeDocumentVersion)return Promise.resolve();let o=i?this.runtimeVersionFloors.get(i)??t:t;return this.versionPersistence=this.versionPersistence.then(()=>s.writeDocumentVersion(o)).catch(()=>{}),this.versionPersistence}handleFileChanged(t){this.handleFilesChanged([t])}handleFilesChanged(t){for(let i of t)this.pendingChangedFiles.add(i);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??Ae;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let t=[...this.pendingChangedFiles].sort((n,i)=>n.localeCompare(i));if(this.pendingChangedFiles.clear(),t.some(Wt)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let n of t)this.runtime?.notifyFileChanged(n)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let t=this.readCurrentSnapshotMarker();!t||t===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(t)).catch(n=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${n instanceof Error?n.message:String(n)}`)})}readCurrentSnapshotMarker(){try{let t=q.readFileSync(b.join(this.options.projectRoot,he),"utf8"),n=JSON.parse(t),i=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!i||fe(i)!==i?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):i}catch(t){return console.warn(`[vcp-site-edit] current-snapshot marker unreadable: ${t instanceof Error?t.message:String(t)}`),null}}async advanceSnapshotId(t){if(this.stopping||!this.isStarted||t===this.options.snapshotId)return;let n=this.options.snapshotId;this.options.snapshotId=t;try{if(this.options.historyRoot){let s=await this.bindHistorySession(t);if(!s.ok)throw new Error(`${s.code}: ${s.message}`)}else this.createRuntime()}catch(s){this.options.snapshotId=n,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${n}: ${s instanceof Error?s.message:String(s)}`);return}let i=new Date().toISOString();for(let s of this.compositions){let o={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:s.context,snapshotId:t,previousSnapshotId:n,at:i};for(let r of this.eventListeners)try{r(o)}catch(c){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${c instanceof Error?c.message:String(c)}`)}}try{this.options.onSnapshotIdChanged?.(t,n)}catch(s){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${s instanceof Error?s.message:String(s)}`)}}refreshCompositions(t=!1){try{let n=Pt(this.options.projectRoot),i=wt(n),s=i.length>0?i:(this.options.runtime.routes??[]).map(a=>({...a})),o=It({files:n});if(o.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=JSON.stringify(o.filter(a=>a.context.effectiveTemplateId!==void 0).map(a=>a.entryFile).sort());if(t&&(this.launchCandidateEntries=r),this.launchCandidateEntries!==r)throw new Error("SITE_EDIT_RUNTIME_RELOAD_REQUIRED: Candidate route collection changed since preview launch");let c=new Map(this.compositions.map(a=>[M(a.context),a])),h=new Map(o.map(a=>[M(a.context),a]));if(this.runtime){for(let[a,l]of c){let u=h.get(a);(!u||u.entryFile!==l.entryFile)&&this.runtime.unregisterComposition(_(l.context))}for(let[a,l]of h){let u=c.get(a);(!u||u.entryFile!==l.entryFile)&&this.runtime.registerComposition(l.context,l.entryFile)}}return this.routes=s,this.compositions=o,this.compositionFiles=n,this.compositionResolutionError=null,this.notifyHealthChanged(),!0}catch(n){let i=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=i,t)throw i;return this.notifyHealthChanged(),!1}}resolveComposition(t){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=Et(t.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let i=this.routes.find(r=>r.routeId===n);if(!i)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let s=St({selector:{routeId:n,...t.templateId!==void 0?{templateId:t.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:i.entryFile}),o=M(s.context);if(!this.compositions.some(r=>M(r.context)===o))throw new Error(`Composition is not registered by the preview bridge: ${o}`);return s}getInitialDocumentVersions(){return this.compositions.flatMap(t=>{let n=this.runtimeVersionFloors.get(M(t.context));return n===void 0?[]:[{composition:t.context,version:n}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function je(e){return Ue(e)}function Ue(e){return{tenantId:e.tenantId,siteId:e.siteId,snapshotId:e.snapshotId,projectRoot:b.resolve(e.projectRoot??process.cwd()),basePath:e.basePath??"",host:e.host??Ct,port:e.port??bt,path:e.path??vt,sessionKey:e.sessionKey??Kt(),historyRoot:e.historyRoot,allowedOrigins:e.allowedOrigins??[],runtime:e.runtime??{},onSnapshotIdChanged:e.onSnapshotIdChanged,onHealthChanged:e.onHealthChanged}}function Tt(e){let t=["app","src/app","components/site","src/components/site","lib/generated","src/lib/generated","public"];return[...new Set([...e?.length?e:t,".vcp"])]}function Pt(e){let t=[],n=["app","src/app",".vcp"],i=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),s=o=>{if(!q.existsSync(o))return;let r;try{r=q.readdirSync(o,{withFileTypes:!0})}catch{return}for(let c of r){if(c.name.startsWith(".")&&c.name!==".vcp")continue;if(c.isDirectory()){if(i.has(c.name))continue;s(b.join(o,c.name));continue}if(!c.isFile())continue;let h=b.join(o,c.name),a="";try{a=q.readFileSync(h,"utf8")}catch{}t.push({path:b.relative(e,h).replaceAll(b.sep,"/"),content:a})}};for(let o of n)s(b.join(e,o));return t.sort((o,r)=>o.path.localeCompare(r.path))}function At(e){if(!e||typeof e!="object"||typeof e.routeId!="string")throw new Error("Normalized composition context is required.");if("effectiveTemplateId"in e)return e;let t=e;return{routeId:t.routeId,...t.templateId?{effectiveTemplateId:t.templateId}:{}}}function _(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function Nt(e){return b.join(b.resolve(e.root),fe(e.siteId),fe(e.snapshotId),ye("sha256").update(e.editingSessionId).digest("hex").slice(0,32))}function fe(e){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e)&&e!=="."&&e!==".."?e:ye("sha256").update(e).digest("hex").slice(0,32)}function Ht(){let e=globalThis;return e[Ne]??=new Map,e[Ne]}function Ot(e){return`${e.projectRoot}:${e.host}:${e.port}:${e.path}:${e.historyRoot??""}`}function de(e,t,n){let i=n??(t?.composition?{routeId:t.composition.routeId,...t.composition.templateId?{effectiveTemplateId:t.composition.templateId}:{}}:void 0);if(!i)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:i,error:{code:e.code,message:e.message},data:{requestId:t?.id??"mutation-context",kind:t?.kind??"update-text",composition:i,target:t?.target??{kind:"node",key:""},resultVersion:t?.documentVersion??0}}}function Se(e){let t=new gt({maxPayload:De,noServer:!0}),n=new WeakSet,i=o=>{new Q({socket:new ge(o),engine:e.engine,validateOperationRequest:r=>Rt.safeParse(r).success})},s=(o,r,c)=>{if(!Mt(o,e))return;let h=Dt(o,e);if(!h){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),r.destroy();return}
|
|
5
|
-
`);return new URL(`data:text/javascript,${encodeURIComponent(n)}`)}function Xt(e){if(!e||!/^\d+$/.test(e.trim()))return;let t=Number(e.trim());return Number.isSafeInteger(t)&&t>0?t:void 0}var en="next",tn="VCP_SITE_EDIT_ENGINE_IN_PROCESS",Je="127.0.0.1",nn=3e3,ee="/__vcp/site-edit/bridge",Ge="/__vcp/site-edit/health",sn="probe",on="render",$e="/_error",te="/__vcp/site-edit/client/",rn=`${te}auto-bootstrap.js`,Qe='data-vcp-site-edit-bootstrap="true"',an=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],cn="@vcp-dev/site-edit-engine/webpack-loader",un=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,dn=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],hn=new Set(["auto-bootstrap.js","client.js","composition-key.js","designer-trace.js","document-codec.js","property-preview.js","protocol.js","window-message.js"]);async function Pi(e={},t={}){let n=t.process??process,i=t.createServer??Re.createServer,s=t.attachBridgeServer??(n.env[tn]==="1"?Be:Ie),o=t.loadNext??Wn,r=z.resolve(e.projectRoot??process.cwd()),c=e.host??Je,p=e.port??qn(n.env.PORT)??nn,a=e.bridgePath??ee,l=Sn(a),u=Bn(),S=e.basePath??n.env.VCP_SITE_PREVIEW_BASE_PATH??"/",d=(e.tenantId??n.env.VCP_TENANT_ID)?.trim()||void 0,f=await o(r),m=pn(r),x=f({dev:!0,dir:r,hostname:c,port:p,...ln(m)}),w=x.getRequestHandler(),E=e.siteId??n.env.VCP_SITE_ID??"018f6f4d-a9b8-7120-8000-000000000120",K=e.snapshotId??n.env.VCP_SNAPSHOT_ID??Ln(r),D=e.historyRoot??n.env.VCP_SITE_EDIT_HISTORY_ROOT,W=D?.trim()?z.resolve(D):void 0,R=null,$={launchId:Zt(),pid:process.pid,snapshotId:K},k=i((P,h)=>{bn({basePath:S,bridgePath:a,bridgeHealthPath:l,bridgeSessionKey:u,getBridgeHealth:()=>R?.getHealth()??An(a),launch:$,runRenderProbe:()=>kn(x),handle:w,request:P,response:h}).catch(g=>{console.error(g),h.headersSent||(h.statusCode=500),h.end("site edit preview request failed")})}),B=null,T=null,V=()=>T||(Z(()=>n.off("SIGINT",j)),Z(()=>n.off("SIGTERM",j)),Z(()=>B?.()),B=null,Z(()=>R?.stop()),R=null,T=Promise.allSettled([Jn(k),Gn(()=>x.close())]).then(()=>{}),T),j=()=>{V()};try{return await x.prepare(),R=s(k,{...d?{tenantId:d}:{},siteId:E,snapshotId:K,projectRoot:r,basePath:S,host:c,port:p,path:a,sessionKey:u,...W?{historyRoot:W}:{},onSnapshotIdChanged:P=>{$.snapshotId=P}}),B=mn({server:k,bridgePath:a,handleUpgrade:x.getUpgradeHandler()}),n.on("SIGINT",j),n.on("SIGTERM",j),await R.whenReady?.(),await zn(k,p,c),{bundler:m,close:V,host:c,port:Qn(k,p),server:k}}catch(P){throw await V(),P}}function pn(e){let t=fn(e);return t&&gn(t)?"webpack":"turbopack"}function ln(e){return e==="webpack"?{webpack:!0}:{turbopack:!0}}function fn(e){for(let t of an){let n=G(e,t);if(F(n))return U(n,"utf8")}return null}function gn(e){return e.includes(cn)?!0:un.test(e)&&dn.some(t=>t.test(e))}function mn(e){let t=(n,i,s)=>{yn(n.url,e.bridgePath)||e.handleUpgrade(n,i,s).catch(o=>{console.error(o),i.destroyed||i.destroy()})};return e.server.on("upgrade",t),()=>e.server.off("upgrade",t)}function yn(e,t){return new URL(e??"/","http://localhost").pathname===t}function Sn(e=ee){if(e===ee)return Ge;let t=(e.startsWith("/")?e:`/${e}`).replace(/\/+$/,"");return t.endsWith("/bridge")?`${t.slice(0,-7)}/health`:t?`${t}/health`:"/health"}function In(e){let t=JSON.stringify({basePath:e.basePath,launch_id:e.launch?.launchId,snapshot_id:e.launch?.snapshotId,wsPath:e.bridgePath,bridge_session_key:e.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:e.runtimeVersions??wn()});return`<script type="module" ${Qe}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(rn)};void startSiteEditAutoBootstrap(window, ${t}).catch(() => undefined);</script>`}function wn(){return{bridge:we("@vcp-dev/site-edit-bridge"),contracts:we("@vcp-dev/contracts"),engine:we("@vcp-dev/site-edit-engine")}}function we(e){let t=Le(import.meta.url);try{let n=t.resolve(e),i=En(n,e);if(!i)return;let s=JSON.parse(U(i,"utf8"));return typeof s.version=="string"?s.version:void 0}catch{return}}function En(e,t){let n=z.parse(e).root,i=J(e);for(;i&&i!==n;){let s=G(i,"package.json");if(F(s))try{if(JSON.parse(U(s,"utf8")).name===t)return s}catch{return}i=J(i)}}function xn(e,t){if(e.includes(Qe))return e;let n=In(t),i=/<\/body\s*>/i.exec(e);return i?`${e.slice(0,i.index)}${n}${e.slice(i.index)}`:`${e}${n}`}function Ai(e,t=J(ze(import.meta.url)),n=F){return Ye(e,t,n)?.path??null}function Ye(e,t=J(ze(import.meta.url)),n=F){let i=new URL(e??"/","http://localhost").pathname;if(!i.startsWith(te))return null;let s=i.slice(te.length);if(!hn.has(s))return null;let o=Ce($n(t),s);if(qe(t)!=="src")return{path:o,requiresTranspile:!1};let r=Ce(t,s.replace(/\.js$/,".ts"));return n(r)?{path:r,requiresTranspile:!0}:{path:o,requiresTranspile:!1}}async function Rn(e,t){let n=Ye(e.url);if(!n)return!1;if(!F(n.path))return t.writeHead(404),t.end("site edit client asset not found"),!0;let i=n.requiresTranspile?await Cn(U(n.path,"utf8")):U(n.path);return t.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),t.end(i),!0}async function Cn(e){let t=await import("typescript");return t.transpileModule(e,{compilerOptions:{module:t.ModuleKind.ES2022,target:t.ScriptTarget.ES2022}}).outputText}async function bn({basePath:e,bridgePath:t,bridgeHealthPath:n,bridgeSessionKey:i,getBridgeHealth:s,launch:o,runRenderProbe:r,handle:c,request:p,response:a}){if(vn(p.url,e)){a.statusCode=404,a.end();return}if(await Pn(p,a,n,s,o,r)||await Rn(p,a))return;let l=Nn(p),u=Hn(p),S=l||u?Dn(a,{basePath:e,bridgePath:t,bridgeSessionKey:i,patchNextDevChunk:u,launch:{...o}}):()=>{};try{await c(p,a)}catch(d){throw S(),d}}function vn(e,t){let n=(e??"/").split(/[?#]/,1)[0]??"/";if(!/(\\|\/\/)/.test(n))return!1;let i;try{i=decodeURIComponent(n).replace(/\\/g,"/").replace(/\/{2,}/g,"/")}catch{return!1}let s=new URL(i,"http://namespace.invalid").pathname,o=t.replace(/\/+$/,"");return(o&&(s===o||s.startsWith(o+"/"))?s.slice(o.length)||"/":s).startsWith("/.vcp")}function _n(e){let t=e instanceof Error?e.message:String(e),n=e&&typeof e=="object"&&"code"in e?e.code:void 0;return/\.next[/\\]/.test(t)||/chunks[/\\]ssr[/\\]/.test(t)?n==="ENOENT"||n==="MODULE_NOT_FOUND"?{verdict:"artifact_broken",detail:t}:t.includes("Cannot find module")?{verdict:"artifact_broken",detail:t}:{verdict:"inconclusive",detail:t}:{verdict:"inconclusive",detail:t}}async function kn(e){let t=new Re.IncomingMessage(new We);t.method="GET",t.url=$e,t.headers={host:Je};let n=new Re.ServerResponse(t);n.assignSocket(new We);try{return await e.renderToHTML(t,n,$e,{}),{verdict:"ok"}}catch(i){return _n(i)}finally{try{let i=n.socket;i&&(n.detachSocket(i),i.destroy())}catch{}}}function Tn(e){return new URL(e??"/","http://localhost").searchParams.get(sn)===on}async function Pn(e,t,n,i,s,o){if(e.method!=="GET"||new URL(e.url??"/","http://localhost").pathname!==n)return!1;let c=i(),p=Tn(e.url)?await o():void 0,a={...c,launch:s,...p?{render:p}:{}};return t.writeHead(c.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify(a)),!0}function An(e){return{ok:!1,bridgePath:e,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function Nn(e){return On(e)?(e.headers["accept-encoding"]="identity",!0):!1}function Hn(e){return Mn(e)?(e.headers["accept-encoding"]="identity",!0):!1}function On(e){if(e.method!=="GET")return!1;let t=new URL(e.url??"/","http://localhost");if(e.headers["x-template-id"]!==void 0&&!t.searchParams.has("template_id"))return!1;let n=t.pathname;if(n.startsWith("/_next/")||n.startsWith(te)||n===ee||n===Ge)return!1;let i=e.headers.accept??"";return i===""||i.includes("text/html")}function Mn(e){if(e.method!=="GET"||!String(e.headers["user-agent"]??"").includes("Firefox"))return!1;let n=new URL(e.url??"/","http://localhost").pathname;return n.startsWith("/_next/static/chunks/")&&n.endsWith(".js")}function Dn(e,t){let n=[],i=e,s=i.flushHeaders.bind(e),o=i.write.bind(e),r=i.end.bind(e),c=!1;i.flushHeaders=()=>{};let p=()=>{if(t.patchNextDevChunk||c)return!1;let a=Buffer.concat(n);if(!/<body\b[^>]*>/iu.test(a.toString("utf8")))return!1;let l=Ve(e,a,t);return l!==a&&!e.headersSent&&e.removeHeader("content-length"),o(l),n.length=0,c=!0,!0};return i.write=(a,l,u)=>c?(a!==void 0&&o(Ee(a,xe(l))),X(l),X(u),!0):(a!==void 0&&(n.push(Ee(a,xe(l))),p()),X(l),X(u),!0),i.end=(a,l,u)=>{let S=typeof a=="function"?void 0:a;if(c)return i.write=o,i.end=r,i.flushHeaders=s,S===void 0?typeof a=="function"?r(a):typeof l=="function"?r(l):typeof u=="function"?r(u):r():typeof l=="function"?r(S,l):typeof u=="function"?r(S,l,u):r(S,l);S!==void 0&&n.push(Ee(S,xe(l))),i.write=o,i.end=r,i.flushHeaders=s;let d=Buffer.concat(n),f=Ve(e,d,t);f!==d&&!e.headersSent&&e.removeHeader("content-length");let m=typeof a=="function"?a:typeof l=="function"?l:typeof u=="function"?u:void 0;return m?r(f,m):r(f)},()=>{i.write=o,i.end=r,i.flushHeaders=s}}function Ve(e,t,n){if(jn(e,t))return Buffer.from(xn(t.toString("utf8"),n));if(n.patchNextDevChunk&&Un(e)){let i=t.toString("utf8"),s=Fn(i);if(s!==i)return Buffer.from(s)}return t}function Bn(){let e=new Uint8Array(32);return globalThis.crypto.getRandomValues(e),Buffer.from(e).toString("base64url")}function jn(e,t){if(e.statusCode<200||e.statusCode>=300)return!1;let n=e.getHeader("content-type"),i=Array.isArray(n)?n.join(";"):String(n??"");return t.toString("utf8",0,256).includes("<html")&&(i===""||i.includes("text/html"))}function Un(e){if(e.statusCode<200||e.statusCode>=300)return!1;let t=e.getHeader("content-type"),n=Array.isArray(t)?t.join(";"):String(t??"");return n===""||n.includes("javascript")||n.includes("ecmascript")}function Fn(e){return[["debugChannel = createDebugChannel(undefined);","debugChannel = undefined;"],["if (isStreamErrorOrUnfinished(ctr)) {","if (false) {"],["return ctr.desiredSize === null || ctr.desiredSize < 0;","return false;"],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join(`
|
|
4
|
+
`),r.destroy();return}h.authenticatedByHeader&&n.add(o),t.handleUpgrade(o,r,c,a=>{t.emit("connection",a,o)})};return e.server.on("upgrade",s),t.on("connection",(o,r)=>{if(n.has(r)){i(o);return}Bt(o,e.sessionKey,()=>{i(o)})}),t.stop=()=>{e.server.off("upgrade",s);for(let o of t.clients)o.close(1001,"bridge stopped");t.close()},t}var ge=class{constructor(t){this.socket=t,this.onmessage=null,this.onclose=null,t.on("error",()=>{}),t.on("message",n=>{this.onmessage?.({data:n.toString()})}),t.on("close",()=>{this.onclose?.()})}send(t){if(this.socket.readyState===Me.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(t,"utf8")>_t){this.socket.close(1013,"bridge backpressure");return}this.socket.send(t)}}close(t,n){this.socket.close(t,n)}};function Mt(e,t){return new URL(e.url??"/","http://localhost").pathname===t.path}function Dt(e,t){if(new URL(e.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let i=pe(e.headers["x-vcp-site-edit-key"]),s=Fe(i,t.sessionKey),o=pe(e.headers.host),r=pe(e.headers.origin);return o&&!jt(o,t.host,r)?null:r?Ft(r,o,t.allowedOrigins)?{authenticatedByHeader:s}:null:s?{authenticatedByHeader:!0}:null}function Bt(e,t,n){let i=setTimeout(()=>{e.close(1008,"bridge authentication timeout")},kt),s,o=()=>{clearTimeout(i),e.off("message",s),e.off("close",o)},r=()=>{o(),e.close(1008,"bridge authentication failed")};s=c=>{try{let h=C.parseMessage(c.toString());if(h.type!=="auth"||!Fe(h.payload.key,t)){r();return}o(),e.readyState===Me.OPEN&&e.send(C.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{r()}},e.on("message",s),e.on("close",o)}function Fe(e,t){if(!e)return!1;let n=Buffer.from(e),i=Buffer.from(t);return n.length===i.length&&ft(n,i)}function jt(e,t,n){let i=Oe(e),s=Oe(t);return i===s||He(s)&&He(i)&&(!n||me(n,e))||Ut(s)&&(!n||me(n,e))}function Ut(e){return e==="0.0.0.0"||e==="::"}function He(e){return e==="localhost"||e==="127.0.0.1"||e==="::1"}function Ft(e,t,n){return n.includes(e)||(t?me(e,t):!1)}function me(e,t){try{return new URL(e).host===Ke(t)}catch{return!1}}function Oe(e){let t=Ke(e);return t.startsWith("[")?t.slice(1,t.indexOf("]")):t.split(":").length-1===1?t.split(":",1)[0]??"":t}function Ke(e){return e.trim().toLowerCase()}function pe(e){return Array.isArray(e)?e[0]:e}function Kt(){return lt(32).toString("base64url")}function Wt(e){let t=e.replaceAll("\\","/");return t===he||t.endsWith(`/${he}`)}var qt="SITE_EDIT_ENGINE_UNAVAILABLE",zt=5e3,Jt=1024,Gt="VCP_SITE_EDIT_ENGINE_MAX_HEAP_MB",Qt=3,Xt=10*6e4,Yt=1012;function Ie(e,t,n={}){let i=je(t),{onSnapshotIdChanged:s,onHealthChanged:o,...r}=i,c={options:r},h=n.maxHeapMb??en(process.env[Gt])??Jt,a=n.maxRestarts??Qt,l=n.restartWindowMs??Xt,u="starting",S,d=0,f=[],y=null,x=0,E=new Map,w=new Set,K,D,W=new Promise((p,g)=>{K=p,D=g});W.catch(()=>{});let R=p=>{let g=new Error(`VCP site edit preview bridge runtime is not ready: ${p}`);return g.code=qt,g},$=p=>{for(let g of E.values())g.reject(p);E.clear()},k=p=>{switch(p.type){case"ready":y=p.health,u==="starting"?(u="ready",K()):u==="restarting"&&(u="ready",console.warn("[vcp-site-edit] engine worker restarted"));return;case"start-failed":u==="starting"&&T(ne(p.error));return;case"result":{let g=E.get(p.id);if(!g)return;E.delete(p.id),p.ok?g.resolve(p.value):g.reject(ne(p.error));return}case"event":for(let g of w)try{g(p.event)}catch(I){console.warn(`[vcp-site-edit] engine event listener failed: ${I instanceof Error?I.message:String(I)}`)}return;case"health":y=p.health;return;case"snapshot-id-changed":c.options.snapshotId=p.next;try{s?.(p.next,p.previous)}catch(g){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${g instanceof Error?g.message:String(g)}`)}return}},B=()=>{let p=d+=1,g=n.workerUrl??new URL("./engine-worker.js",import.meta.url),I=new Lt(Zt(g),{workerData:c,resourceLimits:{maxOldGenerationSizeMb:h},...n.execArgv?{execArgv:n.execArgv}:{}});I.unref();let A=()=>p===d;I.on("message",v=>{A()&&k(v)}),I.on("error",v=>{A()&&T(v instanceof Error?v:new Error(String(v)))}),I.on("exit",v=>{A()&&T(new Error(`engine worker exited with code ${v}`))}),S=I,n.onWorker?.(I)},T=p=>{if(u==="stopped"||u==="failed")return;if($(R(p.message)),y=null,u==="starting"){u="failed",console.error(`[vcp-site-edit] engine worker failed to start: ${p.message}`),D(p);return}let g=Date.now();for(;f.length>0&&g-f[0]>l;)f.shift();if(f.length>=a){u="failed",console.error(`[vcp-site-edit] engine worker stopped unexpectedly and exhausted ${a} restarts: ${p.message}`);return}f.push(g),u="restarting";for(let I of P.clients)I.close(Yt,"site edit engine restarting");console.error(`[vcp-site-edit] engine worker stopped unexpectedly, restarting (${f.length}/${a}): ${p.message}`),B()},V=(p,g)=>{if(u==="failed"||u==="stopped"||u==="restarting")return Promise.reject(R(u));let I=x+=1;return new Promise((A,v)=>{E.set(I,{resolve:A,reject:v});let Ze={type:"call",id:I,method:p,args:g};S.postMessage(Ze)})},j=Object.fromEntries(be.map(p=>[p,(...g)=>V(p,g)])),P=Se({server:e,path:i.path,sessionKey:i.sessionKey,host:i.host,allowedOrigins:i.allowedOrigins,engine:{...j,subscribe(p){return w.add(p),()=>w.delete(p)}}});return B(),{getHealth(){return u==="ready"&&y?y:{ok:!1,bridgePath:i.path,runtimeReady:!1,routesIndexed:y?.routesIndexed??0,sessionRequired:!!i.sessionKey}},stop(){if(u==="stopped")return;let p=u==="starting";u="stopped",P.stop(),p&&D(R("bridge stopped")),$(R("bridge stopped")),w.clear();let g=S,I={type:"stop"};g.postMessage(I);let A=setTimeout(()=>{g.terminate()},zt);A.unref(),g.once("exit",()=>clearTimeout(A))},whenReady(){return W},eventListeners:w}}function Zt(e){if(!e.pathname.endsWith(".ts"))return e;let t=Vt($t(import.meta.url).resolve("tsx/esm/api")).href,n=[`import { tsImport } from ${JSON.stringify(t)};`,`await tsImport(${JSON.stringify(e.href)}, ${JSON.stringify(e.href)});`].join(`
|
|
5
|
+
`);return new URL(`data:text/javascript,${encodeURIComponent(n)}`)}function en(e){if(!e||!/^\d+$/.test(e.trim()))return;let t=Number(e.trim());return Number.isSafeInteger(t)&&t>0?t:void 0}var nn="next",sn="VCP_SITE_EDIT_ENGINE_IN_PROCESS",Je="127.0.0.1",on=3e3,ee="/__vcp/site-edit/bridge",Ge="/__vcp/site-edit/health",rn="probe",an="render",$e="/_error",te="/__vcp/site-edit/client/",cn=`${te}auto-bootstrap.js`,Qe='data-vcp-site-edit-bootstrap="true"',un=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],dn="@vcp-dev/site-edit-engine/webpack-loader",pn=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,hn=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],ln=new Set(["auto-bootstrap.js","client.js","composition-key.js","designer-trace.js","document-codec.js","property-preview.js","protocol.js","window-message.js"]);async function Ni(e={},t={}){let n=t.process??process,i=t.createServer??Re.createServer,s=t.attachBridgeServer??(n.env[sn]==="1"?Be:Ie),o=t.loadNext??Vn,r=z.resolve(e.projectRoot??process.cwd()),c=e.host??Je,h=e.port??Jn(n.env.PORT)??on,a=e.bridgePath??ee,l=En(a),u=Un(),S=e.basePath??n.env.VCP_SITE_PREVIEW_BASE_PATH??"/",d=(e.tenantId??n.env.VCP_TENANT_ID)?.trim()||void 0,f=await o(r),y=fn(r),x=f({dev:!0,dir:r,hostname:c,port:h,...gn(y)}),E=x.getRequestHandler(),w=e.siteId??n.env.VCP_SITE_ID??"018f6f4d-a9b8-7120-8000-000000000120",K=e.snapshotId??n.env.VCP_SNAPSHOT_ID??zn(r),D=e.historyRoot??n.env.VCP_SITE_EDIT_HISTORY_ROOT,W=D?.trim()?z.resolve(D):void 0,R=null,$={launchId:tn(),pid:process.pid,snapshotId:K},k=i((P,p)=>{_n({basePath:S,bridgePath:a,bridgeHealthPath:l,bridgeSessionKey:u,getBridgeHealth:()=>R?.getHealth()??Hn(a),launch:$,runRenderProbe:()=>Pn(x),handle:E,request:P,response:p}).catch(g=>{console.error(g),p.headersSent||(p.statusCode=500),p.end("site edit preview request failed")})}),B=null,T=null,V=()=>T||(Z(()=>n.off("SIGINT",j)),Z(()=>n.off("SIGTERM",j)),Z(()=>B?.()),B=null,Z(()=>R?.stop()),R=null,T=Promise.allSettled([Qn(k),Xn(()=>x.close())]).then(()=>{}),T),j=()=>{V()};try{return await x.prepare(),R=s(k,{...d?{tenantId:d}:{},siteId:w,snapshotId:K,projectRoot:r,basePath:S,host:c,port:h,path:a,sessionKey:u,...W?{historyRoot:W}:{},onSnapshotIdChanged:P=>{$.snapshotId=P}}),B=Sn({server:k,bridgePath:a,handleUpgrade:x.getUpgradeHandler()}),n.on("SIGINT",j),n.on("SIGTERM",j),await R.whenReady?.(),await Gn(k,h,c),{bundler:y,close:V,host:c,port:Yn(k,h),server:k}}catch(P){throw await V(),P}}function fn(e){let t=mn(e);return t&&yn(t)?"webpack":"turbopack"}function gn(e){return e==="webpack"?{webpack:!0}:{turbopack:!0}}function mn(e){for(let t of un){let n=G(e,t);if(F(n))return U(n,"utf8")}return null}function yn(e){return e.includes(dn)?!0:pn.test(e)&&hn.some(t=>t.test(e))}function Sn(e){let t=(n,i,s)=>{In(n.url,e.bridgePath)||e.handleUpgrade(n,i,s).catch(o=>{console.error(o),i.destroyed||i.destroy()})};return e.server.on("upgrade",t),()=>e.server.off("upgrade",t)}function In(e,t){return new URL(e??"/","http://localhost").pathname===t}function En(e=ee){if(e===ee)return Ge;let t=(e.startsWith("/")?e:`/${e}`).replace(/\/+$/,"");return t.endsWith("/bridge")?`${t.slice(0,-7)}/health`:t?`${t}/health`:"/health"}function wn(e){let t=JSON.stringify({basePath:e.basePath,launch_id:e.launch?.launchId,snapshot_id:e.launch?.snapshotId,wsPath:e.bridgePath,bridge_session_key:e.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:e.runtimeVersions??xn()});return`<script type="module" ${Qe}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(cn)};void startSiteEditAutoBootstrap(window, ${t}).catch(() => undefined);</script>`}function xn(){return{bridge:Ee("@vcp-dev/site-edit-bridge"),contracts:Ee("@vcp-dev/contracts"),engine:Ee("@vcp-dev/site-edit-engine")}}function Ee(e){let t=Le(import.meta.url);try{let n=t.resolve(e),i=Rn(n,e);if(!i)return;let s=JSON.parse(U(i,"utf8"));return typeof s.version=="string"?s.version:void 0}catch{return}}function Rn(e,t){let n=z.parse(e).root,i=J(e);for(;i&&i!==n;){let s=G(i,"package.json");if(F(s))try{if(JSON.parse(U(s,"utf8")).name===t)return s}catch{return}i=J(i)}}function Cn(e,t){if(e.includes(Qe))return e;let n=wn(t),i=/<\/body\s*>/i.exec(e);return i?`${e.slice(0,i.index)}${n}${e.slice(i.index)}`:`${e}${n}`}function Hi(e,t=J(ze(import.meta.url)),n=F){return Xe(e,t,n)?.path??null}function Xe(e,t=J(ze(import.meta.url)),n=F){let i=new URL(e??"/","http://localhost").pathname;if(!i.startsWith(te))return null;let s=i.slice(te.length);if(!ln.has(s))return null;let o=Ce(Ln(t),s);if(qe(t)!=="src")return{path:o,requiresTranspile:!1};let r=Ce(t,s.replace(/\.js$/,".ts"));return n(r)?{path:r,requiresTranspile:!0}:{path:o,requiresTranspile:!1}}async function bn(e,t){let n=Xe(e.url);if(!n)return!1;if(!F(n.path))return t.writeHead(404),t.end("site edit client asset not found"),!0;let i=n.requiresTranspile?await vn(U(n.path,"utf8")):U(n.path);return t.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),t.end(i),!0}async function vn(e){let t=await import("typescript");return t.transpileModule(e,{compilerOptions:{module:t.ModuleKind.ES2022,target:t.ScriptTarget.ES2022}}).outputText}async function _n({basePath:e,bridgePath:t,bridgeHealthPath:n,bridgeSessionKey:i,getBridgeHealth:s,launch:o,runRenderProbe:r,handle:c,request:h,response:a}){if(kn(h.url,e)){a.statusCode=404,a.end();return}if(await Nn(h,a,n,s,o,r)||await bn(h,a))return;let l=On(h),u=Mn(h),S=l||u?jn(a,{basePath:e,bridgePath:t,bridgeSessionKey:i,patchNextDevChunk:u,launch:{...o}}):()=>{};try{await c(h,a)}catch(d){throw S(),d}}function kn(e,t){let n=(e??"/").split(/[?#]/,1)[0]??"/";if(!/(\\|\/\/)/.test(n))return!1;let i;try{i=decodeURIComponent(n).replace(/\\/g,"/").replace(/\/{2,}/g,"/")}catch{return!1}let s=new URL(i,"http://namespace.invalid").pathname,o=t.replace(/\/+$/,"");return(o&&(s===o||s.startsWith(o+"/"))?s.slice(o.length)||"/":s).startsWith("/.vcp")}function Tn(e){let t=e instanceof Error?e.message:String(e),n=e&&typeof e=="object"&&"code"in e?e.code:void 0;return/\.next[/\\]/.test(t)||/chunks[/\\]ssr[/\\]/.test(t)?n==="ENOENT"||n==="MODULE_NOT_FOUND"?{verdict:"artifact_broken",detail:t}:t.includes("Cannot find module")?{verdict:"artifact_broken",detail:t}:{verdict:"inconclusive",detail:t}:{verdict:"inconclusive",detail:t}}async function Pn(e){let t=new Re.IncomingMessage(new We);t.method="GET",t.url=$e,t.headers={host:Je};let n=new Re.ServerResponse(t);n.assignSocket(new We);try{return await e.renderToHTML(t,n,$e,{}),{verdict:"ok"}}catch(i){return Tn(i)}finally{try{let i=n.socket;i&&(n.detachSocket(i),i.destroy())}catch{}}}function An(e){return new URL(e??"/","http://localhost").searchParams.get(rn)===an}async function Nn(e,t,n,i,s,o){if(e.method!=="GET"||new URL(e.url??"/","http://localhost").pathname!==n)return!1;let c=i(),h=An(e.url)?await o():void 0,a={...c,launch:s,...h?{render:h}:{}};return t.writeHead(c.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify(a)),!0}function Hn(e){return{ok:!1,bridgePath:e,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function On(e){return Dn(e)?(e.headers["accept-encoding"]="identity",!0):!1}function Mn(e){return Bn(e)?(e.headers["accept-encoding"]="identity",!0):!1}function Dn(e){if(e.method!=="GET")return!1;let t=new URL(e.url??"/","http://localhost");if(e.headers["x-template-id"]!==void 0&&!t.searchParams.has("template_id"))return!1;let n=t.pathname;if(n.startsWith("/_next/")||n.startsWith(te)||n===ee||n===Ge)return!1;let i=e.headers.accept??"";return i===""||i.includes("text/html")}function Bn(e){if(e.method!=="GET"||!String(e.headers["user-agent"]??"").includes("Firefox"))return!1;let n=new URL(e.url??"/","http://localhost").pathname;return n.startsWith("/_next/static/chunks/")&&n.endsWith(".js")}function jn(e,t){let n=[],i=e,s=i.flushHeaders.bind(e),o=i.write.bind(e),r=i.end.bind(e),c=!1;i.flushHeaders=()=>{};let h=()=>{if(t.patchNextDevChunk||c)return!1;let a=Buffer.concat(n);if(!/<body\b[^>]*>/iu.test(a.toString("utf8")))return!1;let l=Ve(e,a,t);return l!==a&&!e.headersSent&&e.removeHeader("content-length"),o(l),n.length=0,c=!0,!0};return i.write=(a,l,u)=>c?(a!==void 0&&o(we(a,xe(l))),Y(l),Y(u),!0):(a!==void 0&&(n.push(we(a,xe(l))),h()),Y(l),Y(u),!0),i.end=(a,l,u)=>{let S=typeof a=="function"?void 0:a;if(c)return i.write=o,i.end=r,i.flushHeaders=s,S===void 0?typeof a=="function"?r(a):typeof l=="function"?r(l):typeof u=="function"?r(u):r():typeof l=="function"?r(S,l):typeof u=="function"?r(S,l,u):r(S,l);S!==void 0&&n.push(we(S,xe(l))),i.write=o,i.end=r,i.flushHeaders=s;let d=Buffer.concat(n),f=Ve(e,d,t);f!==d&&!e.headersSent&&e.removeHeader("content-length");let y=typeof a=="function"?a:typeof l=="function"?l:typeof u=="function"?u:void 0;return y?r(f,y):r(f)},()=>{i.write=o,i.end=r,i.flushHeaders=s}}function Ve(e,t,n){if(Fn(e,t))return Buffer.from(Cn(t.toString("utf8"),n));if(n.patchNextDevChunk&&Kn(e)){let i=t.toString("utf8"),s=Wn(i);if(s!==i)return Buffer.from(s)}return t}function Un(){let e=new Uint8Array(32);return globalThis.crypto.getRandomValues(e),Buffer.from(e).toString("base64url")}function Fn(e,t){if(e.statusCode<200||e.statusCode>=300)return!1;let n=e.getHeader("content-type"),i=Array.isArray(n)?n.join(";"):String(n??"");return t.toString("utf8",0,256).includes("<html")&&(i===""||i.includes("text/html"))}function Kn(e){if(e.statusCode<200||e.statusCode>=300)return!1;let t=e.getHeader("content-type"),n=Array.isArray(t)?t.join(";"):String(t??"");return n===""||n.includes("javascript")||n.includes("ecmascript")}function Wn(e){return[["debugChannel = createDebugChannel(undefined);","debugChannel = undefined;"],["if (isStreamErrorOrUnfinished(ctr)) {","if (false) {"],["return ctr.desiredSize === null || ctr.desiredSize < 0;","return false;"],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join(`
|
|
6
6
|
`),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join(`
|
|
7
|
-
`)],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join("\\n"),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join("\\n")],["debugChannel: createDebugChannel && createDebugChannel(requestHeaders)","debugChannel: undefined"],["debugChannel: createDebugChannel && createDebugChannel(headers)","debugChannel: undefined"]].reduce((n,[i,s])
|
|
7
|
+
`)],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join("\\n"),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join("\\n")],["debugChannel: createDebugChannel && createDebugChannel(requestHeaders)","debugChannel: undefined"],["debugChannel: createDebugChannel && createDebugChannel(headers)","debugChannel: undefined"]].reduce((n,[i,s])=>$n(n,i,s),e)}function $n(e,t,n){if(n.length>t.length)throw new Error("Replacement must not be longer than the search string.");return e.replaceAll(t,n.padEnd(t.length," "))}function we(e,t){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e):Buffer.from(String(e),t)}function xe(e){return typeof e=="string"?e:void 0}function Y(e){typeof e=="function"&&e()}async function Vn(e){let n=Le(G(e,"package.json"))(nn);return n.default??n}function Ln(e){return qe(e)!=="src"?e:Ce(qn(e),"dist")}function qn(e){return Ye(e)?z.posix.dirname(e):J(e)}function Ce(e,t){return Ye(e)?z.posix.join(e,t):G(e,t)}function Ye(e){return e.includes("/")&&!e.includes("\\")}function zn(e){let t=G(e,".vcp/current-snapshot.json");if(!F(t))return"sandbox_worktree";try{let n=JSON.parse(U(t,"utf8"));return typeof n.snapshot_id=="string"&&n.snapshot_id?n.snapshot_id:typeof n.snapshotId=="string"&&n.snapshotId?n.snapshotId:"sandbox_worktree"}catch{return"sandbox_worktree"}}function Jn(e){if(!e)return null;let t=Number.parseInt(e,10);return Number.isInteger(t)&&t>0&&t<=65535?t:null}function Gn(e,t,n){return new Promise((i,s)=>{e.once("error",s),e.listen(t,n,()=>{e.off("error",s),i()})})}function Qn(e){return new Promise(t=>{e.close(()=>t())})}function Z(e){try{e()}catch{}}function Xn(e){try{return e()}catch(t){return Promise.reject(t)}}function Yn(e,t){let n=e.address();return typeof n=="object"&&n?n.port:t}export{Sn as attachNextDevUpgradeHandler,Ie as attachSiteEditBridgeServerInWorker,Tn as classifySiteEditRenderProbeError,wn as createSiteEditAutoBootstrapHtmlSnippet,Cn as injectSiteEditAutoBootstrap,An as isSiteEditRenderProbeRequested,Wn as patchNextFirefoxDevChunk,Mn as prepareFirefoxNextDevChunkPatchRequest,On as prepareSiteEditHtmlInjectionRequest,En as resolveSiteEditBridgeHealthPath,Xe as resolveSiteEditClientAsset,Hi as resolveSiteEditClientAssetPath,fn as resolveSiteEditNextDevBundler,Pn as runSiteEditRenderProbe,Ni as startSiteEditNextDevServer,vn as transpileSiteEditClientSource};
|
package/dist/protocol.d.ts
CHANGED
|
@@ -102,7 +102,23 @@ export type SiteEditBridgeAuthOkMessage = {
|
|
|
102
102
|
ok: true;
|
|
103
103
|
};
|
|
104
104
|
};
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* 传输层心跳:只证明这条 WebSocket 两端都还活着,由 socket 层在入队前直接应答,
|
|
107
|
+
* 不经过引擎,引擎忙时也不会把慢请求误判成断线。
|
|
108
|
+
*/
|
|
109
|
+
export type SiteEditBridgePingMessage = {
|
|
110
|
+
type: "ping";
|
|
111
|
+
payload: {
|
|
112
|
+
id: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
export type SiteEditBridgePongMessage = {
|
|
116
|
+
type: "pong";
|
|
117
|
+
payload: {
|
|
118
|
+
id: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
export type SiteEditBridgeMessage = SiteEditBridgeAuthMessage | SiteEditBridgeAuthOkMessage | SiteEditBridgePingMessage | SiteEditBridgePongMessage | SiteEditBridgeRequest | SiteEditBridgeResponse | SiteEditBridgeError | SiteEditBridgeEventMessage;
|
|
106
122
|
/**
|
|
107
123
|
* Bridge engine contract. Every socket request is resolved to a normalized
|
|
108
124
|
* composition context before the engine is called; no engine method accepts a
|
package/dist/protocol.js
CHANGED
|
@@ -54,6 +54,9 @@ export class SiteEditBridgeTransport {
|
|
|
54
54
|
if (value.type === "auth") {
|
|
55
55
|
return isRecord(value.payload) && typeof value.payload.key === "string";
|
|
56
56
|
}
|
|
57
|
+
if (value.type === "ping" || value.type === "pong") {
|
|
58
|
+
return isRecord(value.payload) && typeof value.payload.id === "string";
|
|
59
|
+
}
|
|
57
60
|
return (value.type === "auth-ok" &&
|
|
58
61
|
isRecord(value.payload) &&
|
|
59
62
|
value.payload.ok === true);
|
|
@@ -73,6 +76,11 @@ export class SiteEditBridgeSocketServer {
|
|
|
73
76
|
this.handleEngineEvent(event);
|
|
74
77
|
});
|
|
75
78
|
options.socket.onmessage = (event) => {
|
|
79
|
+
const pingId = readPingId(event.data);
|
|
80
|
+
if (pingId !== null) {
|
|
81
|
+
this.sendJson({ type: "pong", payload: { id: pingId } });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
76
84
|
try {
|
|
77
85
|
void this.messageQueue.enqueue(() => this.handleRawMessage(event.data));
|
|
78
86
|
}
|
|
@@ -319,6 +327,25 @@ export class SiteEditBridgeSocketServer {
|
|
|
319
327
|
}
|
|
320
328
|
}
|
|
321
329
|
}
|
|
330
|
+
const MAX_PING_MESSAGE_LENGTH = 256;
|
|
331
|
+
/** 心跳帧很短,先按长度和字面量粗筛,避免对大请求多做一次 JSON 解析。 */
|
|
332
|
+
function readPingId(raw) {
|
|
333
|
+
if (raw.length > MAX_PING_MESSAGE_LENGTH || !raw.includes('"ping"')) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const parsed = JSON.parse(raw);
|
|
338
|
+
return isRecord(parsed) &&
|
|
339
|
+
parsed.type === "ping" &&
|
|
340
|
+
isRecord(parsed.payload) &&
|
|
341
|
+
typeof parsed.payload.id === "string"
|
|
342
|
+
? parsed.payload.id
|
|
343
|
+
: null;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
322
349
|
class BridgeProtocolError extends Error {
|
|
323
350
|
constructor(code, message) {
|
|
324
351
|
super(message);
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import ne from"node:http";import v from"node:fs";import{createHash as j,randomBytes as Se,timingSafeEqual as we}from"node:crypto";import m from"node:path";import{WebSocket as ie,WebSocketServer as Ce}from"ws";import{createPreviewSiteEditEngineRuntime as Ee,openFileHistoryStore as Re,resolveManagedPageTemplateComposition as be,scanSiteEditCompositionsFromFiles as xe}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as ve,scanNextAppRouterRoutesFromFiles as ke}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as _e}from"@vcp-dev/site-edit-engine/source-watcher";function J(t){let e=new Map,n=[],i=new Map,o=[],s=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(s.map((c,d)=>[c,d])),a=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(c=>({placementKey:c.placementKey,placementUnitKey:c.placementUnitKey,relation:c.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},p=t.entries.flatMap((c,d)=>c.dynamicDataSummary?[[d,c.dynamicDataSummary]]:[]),u=new Map(t.entries.map((c,d)=>[c.key,d])),l=[];for(let[c,d]of t.entries.entries()){let f=d.pageCompositionActionScope;if(!f)continue;if(f.kind!=="source-siblings"){l.push([c,{kind:f.kind}]);continue}let _=f.siblingKeys.map(R=>{let b=u.get(R);if(b===void 0)throw new Error("Render entry "+d.key+" references missing source sibling "+R);return b});l.push([c,{kind:"source-siblings",siblingEntryIndexes:_}])}let S=c=>{let d=e.get(c);if(typeof d=="number")return d;let f=n.length;return n.push(c),e.set(c,f),f},z=c=>{let d=null;for(let f of de(c)){let _=`${d??"root"}\0${f}`,R=i.get(_);if(typeof R=="number"){d=R;continue}let b=o.length;o.push([d,S(f)]),i.set(_,b),d=b}if(d===null)throw new Error("Document key must not be empty");return d};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,composition:{...t.composition},routeId:t.composition.routeId,version:t.version,pageComposition:a,tokens:n,nodes:o,entries:t.entries.map(c=>{let d=c.pageCompositionUnitKey?r.get(c.pageCompositionUnitKey)??-1:-1;if(c.pageCompositionUnitKey&&d<0)throw new Error(`Render entry ${c.key} references unknown page composition unit ${c.pageCompositionUnitKey}`);return[z(c.key),ue(c.operationSummary),...d>=0?[d]:[]]}),...l.length>0?{pageCompositionActionScopes:l}:{},...t.entries.every(c=>c.parentKey!==void 0)?{entryParentNodeIds:t.entries.map(c=>c.parentKey===null?null:z(c.parentKey))}:{},...p.length>0?{dynamicDataSummaries:p}:{}}}function ue(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function de(t){if(!t)return[];let e=[],n=0,i=L(t,n);if(i<0)return e.push(t),e;for(e.push(t.slice(0,i)),n=i;n<t.length;){if(i=L(t,n+(t.startsWith("::",n)?2:1)),i<0){e.push(t.slice(n));break}e.push(t.slice(n,i)),n=i}return e}function L(t,e){let n=t.indexOf("/",e),i=t.indexOf("::",e);return n<0?i:i<0?n:Math.min(n,i)}var Q="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",N="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var g=class t{constructor(){}static parseMessage(e,n={}){let i=JSON.parse(e);if(!t.isMessage(i,n))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(h(e)&&h(e.payload)){let n=typeof e.payload.id=="string"?e.payload.id:"unknown",i=this.isRequestKind(e.payload.kind)?e.payload.kind:"get-document";return{id:n,kind:i}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="initialize-session"||e==="get-document"||e==="get-history-status"||e==="get-style-context"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="get-operation-result"||e==="undo"||e==="redo"}static isMessage(e,n={}){return!h(e)||typeof e.type!="string"?!1:e.type==="request"?he(e.payload,n):e.type==="response"||e.type==="error"?h(e.payload)&&typeof e.payload.id=="string":e.type==="event"?h(e.payload)&&h(e.payload.event):e.type==="auth"?h(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"&&h(e.payload)&&e.payload.ok===!0}},T=class{constructor(e){this.options=e,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new B,this.eventDispatcher=new F(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.handleEngineEvent(n)}),e.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(i){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:i instanceof Error?i.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe(),this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.clear()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let i=n.payload,{id:o}=i;switch(i.kind){case"initialize-session":{let s=H(i),r=await this.resolve(s),a=await this.options.engine.initializeSession({composition:r.context,...i.mutationContext?{mutationContext:i.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:i.kind,result:I({...a,data:{...a.data,document:Y(a.data.document)}},r.context)}});return}case"get-document":{let s=await this.resolveWireContext(i),r=await this.options.engine.getDocument(s);this.sendResult(o,i.kind,ye(r,s));return}case"get-history-status":{let s=await this.resolveWireContext(i),r=await this.options.engine.getHistoryStatus(s);this.sendResult(o,i.kind,I(r,s));return}case"get-style-context":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getStyleContext(s),s));return}case"get-object-edit-context":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getObjectEditContext(i.key,s,i.selectionRef),s));return}case"get-class-name-source":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getClassNameSource(i.key,s),s));return}case"get-source-tree":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getSourceTree(s),s));return}case"get-source-file":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getSourceFile({path:i.path,composition:s}),s));return}case"resolve-source-location":{let s=await this.resolveWireContext(i),r=I(await this.options.engine.resolveSourceLocation({key:i.key,composition:s}),s);this.sendResult(o,i.kind,r);return}case"execute":case"get-operation-result":{let s=await this.resolveOperationWireContext(i.request),r=await this.resolveWireContext(i);if(w(s)!==w(r))throw new C(Q,"The execute request composition does not match its operation composition.");let a=await this.normalizeOperationRequest(i.request,s);if(i.kind==="get-operation-result"){this.sendResult(o,i.kind,await this.options.engine.getOperationResult?.(a)??null);return}await this.handleBufferedOperation(o,i.kind,()=>this.options.engine.execute(a));return}case"undo":case"redo":{let s=await this.resolveWireContext(i);await this.handleBufferedOperation(o,i.kind,()=>i.kind==="undo"?this.options.engine.undo(s):this.options.engine.redo(s));return}}}catch(n){let i=g.extractRequestInfo(e);this.sendJson({type:"error",payload:{...i,message:K(n),code:D(n)}})}}async resolve(e){return this.options.engine.resolveComposition(e)}async resolveAndAssert(e){let n=await this.resolve(e),i=this.session.activeComposition;if(!i)throw new C(N,"Initialize a composition session before issuing this request.");if(w(i)!==w(n.context))throw new C(Q,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(e){let n=H(e);return this.resolveAndAssert(n)}async resolveOperationWireContext(e){let n=ge(e);return this.resolveAndAssert(n)}activateSession(e){this.session.activeComposition=e,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(w(e))}async normalizeOperationRequest(e,n){let i={...e,composition:G(n)};if(i.target.kind==="route"){let o=i.target,s=H(o),r=await this.resolveAndAssert(s);i.target={...o,composition:G(r)}}return i}handleEngineEvent(e){let n=e?.composition;if(!fe(n))return;let i=w(n);this.session.subscribedCompositionKeys.has(i)&&(this.eventDispatcher.push(e),e.type==="site-edit.graph.invalidated"&&i===me(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(i)))}sendResult(e,n,i){this.sendJson({type:"response",payload:{id:e,kind:n,result:i}})}async handleRawMessage(e){try{await this.handleMessage(g.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let i=Ie(e);this.sendJson({type:"error",payload:{...i,message:K(n),code:D(n)}})}}sendJson(e){this.options.socket.send(g.stringifyMessage(e))}async handleBufferedOperation(e,n,i){this.eventDispatcher.beginBuffering();try{let o=await i();this.sendResult(e,n,o)}catch(o){this.sendJson({type:"error",payload:{id:e,kind:n,message:K(o),code:D(o)}})}finally{this.eventDispatcher.flush()}}},C=class extends Error{constructor(e,n){super(n),this.code=e,this.name="BridgeProtocolError"}},F=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let n={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(n=>this.send(n))}},B=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await e()},i=this.queue.then(n,n);return this.queue=i.then(()=>{},()=>{}),i}};function he(t,e){if(!h(t)||typeof t.id!="string"||!g.isRequestKind(t.kind))return!1;let n=x(t.composition);switch(t.kind){case"initialize-session":return n&&(t.mutationContext===void 0||M(t.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof t.key=="string"&&n&&pe(t.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof t.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof t.path=="string"&&n;case"execute":case"get-operation-result":return n&&le(t.request)&&(t.request.mutationContext===void 0||M(t.request.mutationContext))&&(e.validateOperationRequest?.(t.request)??!0);case"undo":case"redo":return n&&t.mutationContext===void 0||n&&M(t.mutationContext)}}function pe(t){if(t===void 0)return!0;if(!h(t)||typeof t.key!="string"||t.key.length===0)return!1;let e=t.instanceIndex,n=t.instanceCount;return e!==void 0&&(typeof e!="number"||!Number.isInteger(e)||e<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof e=="number"&&typeof n=="number"&&e>=n?!1:Object.keys(t).every(i=>i==="key"||i==="instanceIndex"||i==="instanceCount")}function le(t){return h(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&x(t.composition)&&typeof t.kind=="string"&&h(t.target)&&(t.target.kind!=="route"||x(t.target.composition))&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&h(t.params)}function x(t){return h(t)&&typeof t.routeId=="string"&&t.routeId.length>0&&(t.templateId===void 0||typeof t.templateId=="string"&&t.templateId.length>0)&&Object.keys(t).every(e=>e==="routeId"||e==="templateId")}function fe(t){return h(t)&&typeof t.routeId=="string"&&(t.effectiveTemplateId===void 0||typeof t.effectiveTemplateId=="string")}function H(t){if(x(t.composition))return t.composition;throw new C(N,"A composition selector is required for this request.")}function ge(t){if(h(t)&&x(t.composition))return t.composition;throw new C(N,"A composition selector is required for execute.")}function G(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function me(t){return t.activeComposition?w(t.activeComposition):null}function w(t){return JSON.stringify([t.routeId,t.effectiveTemplateId??null])}function I(t,e){return h(t)&&t.ok===!0?{...t,composition:e}:t}function ye(t,e){return!h(t)||t.ok!==!0?t:{...t,...e?{composition:e}:{},data:Y(t.data)}}function Y(t){let e=t;return J(e.routeId?e:{...e,routeId:e.composition.routeId})}function h(t){return typeof t=="object"&&t!==null}function M(t){return h(t)&&typeof t.editingSessionId=="string"&&t.editingSessionId.length>0&&typeof t.lockId=="string"&&t.lockId.length>0&&typeof t.ownerId=="string"&&t.ownerId.length>0&&typeof t.leaseAssertion=="string"&&t.leaseAssertion.length>0}function D(t){if(!(!h(t)||typeof t.code!="string"||t.code.length===0))return t.code}function K(t){return t instanceof Error||h(t)&&typeof t.message=="string"&&t.message.length>0?t.message:"Unknown websocket operation failure"}function Ie(t){try{return g.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var O=class{constructor(){this.tail=Promise.resolve()}run(e){let n=this.tail.then(e,e);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as Te,computeSiteEditCompositionKey as E}from"@vcp-dev/contracts";var Oe="127.0.0.1",Ae=30218,He="/__vcp/site-edit/bridge",se=6*1024*1024,Me=se*2,De=3e3,Z=50,U=".vcp/current-snapshot.json",X="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function ut(t){let e=A(t),n=re(e),i=oe(),o=i.get(n);if(o)return o;let s=new k(e);i.set(n,s);try{s.start()}catch(r){throw i.delete(n),s.stop(),r}return s}function dt(t,e,n={}){let i=new k(A(e),t,!1,n);return i.start({listen:!1}),i}var k=class{constructor(e,n=ne.createServer((s,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),i=!0,o={}){this.options=e,this.server=n,this.closeHttpServerOnStop=i,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new O,this.operationReceipts=new Map,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.launchCandidateEntries=null,this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.stopping=!1,this.runtimeVersionFloors=new Map,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve(),this.bridgeEngine=null,this.lastNotifiedHealth=null}start(e={}){if(!this.isStarted){this.stopping=!1;try{v.mkdirSync(m.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(n){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${n instanceof Error?n.message:String(n)}`)}if(this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime(),this.bridgeEngine=this.createBridgeEngine(),e.webSocket!==!1){let n=Pe({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.bridgeEngine});this.stopWebSocketServer=n.stop}this.stopWatching=new _e({projectRoot:this.options.projectRoot,watch_dirs:Ke(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??Z,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.notifyHealthChanged()}}getEngine(){if(!this.bridgeEngine)throw new Error("VCP site edit preview bridge runtime is not ready");return this.bridgeEngine}getHealth(){let e=!!this.runtime&&!this.compositionResolutionError;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopping=!0,this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.closeHttpServerOnStop&&this.server.close();let e=oe(),n=re(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1,this.notifyHealthChanged()}notifyHealthChanged(){let e=this.options.onHealthChanged;if(!e)return;let n=this.getHealth(),i=JSON.stringify(n);if(i!==this.lastNotifiedHealth){this.lastNotifiedHealth=i;try{e(n)}catch(o){console.warn(`[vcp-site-edit] onHealthChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}}createRuntime(e,n=null){let i=this.runtime;this.activeHistoryStore=e??null;let o=(this.dependencies.createRuntime??Ee)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:s=>{"version"in s&&typeof s.version=="number"&&this.recordRuntimeVersion(s.version,"composition"in s?s.composition:void 0);for(let r of this.eventListeners)r(s)},...e?{historyStore:e}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=n,i?.dispose(),this.notifyHealthChanged()}createBridgeEngine(){return{resolveComposition:e=>this.resolveComposition(e),initializeSession:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)throw new Error(`${n.code}: ${n.message}`);let i=Be(e.composition),o=this.getRuntime(),s=y(i);await o.initializeComposition(s);let a=o.getDocument(s).data;await this.recordRuntimeVersion(a.version,i);let p=(await o.getHistoryStatus()).data;return{ok:!0,composition:i,data:{document:a,historyStatus:p}}}),getDocument:async e=>{let n=this.getRuntime(),i=y(e);await n.initializeComposition(i);let o=n.getDocument(i);return await this.recordRuntimeVersion(o.data.version,e),{...o,composition:e}},getHistoryStatus:async e=>({...await this.getRuntime().getHistoryStatus(),composition:e}),getStyleContext:async e=>this.getRuntime().getStyleContext(y(e)),getObjectEditContext:async(e,n,i)=>this.getRuntime().getObjectEditContext(e,y(n),i),getClassNameSource:async(e,n)=>this.getRuntime().getClassNameSource(e,y(n)),getSourceTree:async e=>this.getRuntime().getSourceTree(y(e)),getSourceFile:async e=>this.getRuntime().getSourceFile({path:e.path,composition:y(e.composition)}),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,composition:y(e.composition)}),execute:(e,n)=>this.executeWithReceipt(e,()=>this.mutationQueue.run(async()=>{let i=await this.bindHistorySession(this.options.snapshotId);if(!i.ok)return P(i,e);let o=await this.getRuntime().executeOperation(e);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o})),getOperationResult:e=>{if(e.siteId!==this.options.siteId||e.baseSnapshotId!==this.options.snapshotId)return null;let n=this.operationReceipts.get(this.operationReceiptKey(e));return n?.fingerprint===this.operationFingerprint(e)?n.result:null},undo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return P(n,void 0,e);let i=await this.getRuntime().undo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??e),i.composition?i:{...i,composition:e}}),redo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return P(n,void 0,e);let i=await this.getRuntime().redo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??e),i.composition?i:{...i,composition:e}}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}operationReceiptKey(e){return JSON.stringify([e.siteId,e.baseSnapshotId,e.id])}operationFingerprint(e){return j("sha256").update(JSON.stringify(e)).digest("hex")}executeWithReceipt(e,n){if(e.siteId!==this.options.siteId||e.baseSnapshotId!==this.options.snapshotId)return n();let i=this.operationReceiptKey(e),o=this.operationFingerprint(e),s=this.operationReceipts.get(i);if(s)return s.fingerprint!==o?Promise.reject(new Error("Operation ID was reused with a different request.")):s.result;let r={fingerprint:o,result:n(),settled:!1};return this.operationReceipts.set(i,r),r.result.then(()=>{r.settled=!0;for(let[a,p]of this.operationReceipts){if(this.operationReceipts.size<=256)break;p.settled&&this.operationReceipts.delete(a)}},()=>{this.operationReceipts.delete(i)}),r.result}async bindHistorySession(e){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===e)return{ok:!0};let n=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===e)return;let i=await(this.dependencies.openHistoryStore??Re)(Ne({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let o=i.readDocumentVersion?await i.readDocumentVersion():0,s=this.compositions[0]?.context;if(s){let r=E(s);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(i,e)});this.historySessionTransition=n.catch(()=>{});try{return await n,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(e,n){let i=n?E(n):null;i&&this.runtimeVersionFloors.set(i,Math.max(this.runtimeVersionFloors.get(i)??0,e));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let s=i?this.runtimeVersionFloors.get(i)??e:e;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(s)).catch(()=>{}),this.versionPersistence}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let i of e)this.pendingChangedFiles.add(i);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??Z;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((n,i)=>n.localeCompare(i));if(this.pendingChangedFiles.clear(),e.some(Le)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let n of e)this.runtime?.notifyFileChanged(n)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let e=this.readCurrentSnapshotMarker();!e||e===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(e)).catch(n=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${n instanceof Error?n.message:String(n)}`)})}readCurrentSnapshotMarker(){try{let e=v.readFileSync(m.join(this.options.projectRoot,U),"utf8"),n=JSON.parse(e),i=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!i||q(i)!==i?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):i}catch(e){return console.warn(`[vcp-site-edit] current-snapshot marker unreadable: ${e instanceof Error?e.message:String(e)}`),null}}async advanceSnapshotId(e){if(this.stopping||!this.isStarted||e===this.options.snapshotId)return;let n=this.options.snapshotId;this.options.snapshotId=e;try{if(this.options.historyRoot){let o=await this.bindHistorySession(e);if(!o.ok)throw new Error(`${o.code}: ${o.message}`)}else this.createRuntime()}catch(o){this.options.snapshotId=n,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${n}: ${o instanceof Error?o.message:String(o)}`);return}let i=new Date().toISOString();for(let o of this.compositions){let s={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:e,previousSnapshotId:n,at:i};for(let r of this.eventListeners)try{r(s)}catch(a){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${a instanceof Error?a.message:String(a)}`)}}try{this.options.onSnapshotIdChanged?.(e,n)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(e=!1){try{let n=Fe(this.options.projectRoot),i=ke(n),o=i.length>0?i:(this.options.runtime.routes??[]).map(u=>({...u})),s=xe({files:n});if(s.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=JSON.stringify(s.filter(u=>u.context.effectiveTemplateId!==void 0).map(u=>u.entryFile).sort());if(e&&(this.launchCandidateEntries=r),this.launchCandidateEntries!==r)throw new Error("SITE_EDIT_RUNTIME_RELOAD_REQUIRED: Candidate route collection changed since preview launch");let a=new Map(this.compositions.map(u=>[E(u.context),u])),p=new Map(s.map(u=>[E(u.context),u]));if(this.runtime){for(let[u,l]of a){let S=p.get(u);(!S||S.entryFile!==l.entryFile)&&this.runtime.unregisterComposition(y(l.context))}for(let[u,l]of p){let S=a.get(u);(!S||S.entryFile!==l.entryFile)&&this.runtime.registerComposition(l.context,l.entryFile)}}return this.routes=o,this.compositions=s,this.compositionFiles=n,this.compositionResolutionError=null,this.notifyHealthChanged(),!0}catch(n){let i=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=i,e)throw i;return this.notifyHealthChanged(),!1}}resolveComposition(e){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=ve(e.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let i=this.routes.find(r=>r.routeId===n);if(!i)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let o=be({selector:{routeId:n,...e.templateId!==void 0?{templateId:e.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:i.entryFile}),s=E(o.context);if(!this.compositions.some(r=>E(r.context)===s))throw new Error(`Composition is not registered by the preview bridge: ${s}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(e=>{let n=this.runtimeVersionFloors.get(E(e.context));return n===void 0?[]:[{composition:e.context,version:n}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function ht(t){let e=new k(A(t),ne.createServer(),!1);return e.start({listen:!1,webSocket:!1}),{engine:e.getEngine(),getHealth:()=>e.getHealth(),stop:()=>e.stop()}}function pt(t){return A(t)}function A(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:m.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??Oe,port:t.port??Ae,path:t.path??He,sessionKey:t.sessionKey??ze(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{},onSnapshotIdChanged:t.onSnapshotIdChanged,onHealthChanged:t.onHealthChanged}}function Ke(t){let e=["app","src/app","components/site","src/components/site","lib/generated","src/lib/generated","public"];return[...new Set([...t?.length?t:e,".vcp"])]}function Fe(t){let e=[],n=["app","src/app",".vcp"],i=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=s=>{if(!v.existsSync(s))return;let r;try{r=v.readdirSync(s,{withFileTypes:!0})}catch{return}for(let a of r){if(a.name.startsWith(".")&&a.name!==".vcp")continue;if(a.isDirectory()){if(i.has(a.name))continue;o(m.join(s,a.name));continue}if(!a.isFile())continue;let p=m.join(s,a.name),u="";try{u=v.readFileSync(p,"utf8")}catch{}e.push({path:m.relative(t,p).replaceAll(m.sep,"/"),content:u})}};for(let s of n)o(m.join(t,s));return e.sort((s,r)=>s.path.localeCompare(r.path))}function Be(t){if(!t||typeof t!="object"||typeof t.routeId!="string")throw new Error("Normalized composition context is required.");if("effectiveTemplateId"in t)return t;let e=t;return{routeId:e.routeId,...e.templateId?{effectiveTemplateId:e.templateId}:{}}}function y(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function Ne(t){return m.join(m.resolve(t.root),q(t.siteId),q(t.snapshotId),j("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function q(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:j("sha256").update(t).digest("hex").slice(0,32)}function oe(){let t=globalThis;return t[X]??=new Map,t[X]}function re(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function P(t,e,n){let i=n??(e?.composition?{routeId:e.composition.routeId,...e.composition.templateId?{effectiveTemplateId:e.composition.templateId}:{}}:void 0);if(!i)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:i,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",composition:i,target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function Pe(t){let e=new Ce({maxPayload:se,noServer:!0}),n=new WeakSet,i=s=>{new T({socket:new W(s),engine:t.engine,validateOperationRequest:r=>Te.safeParse(r).success})},o=(s,r,a)=>{if(!Ve(s,t))return;let p=Ue(s,t);if(!p){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import ne from"node:http";import k from"node:fs";import{createHash as j,randomBytes as Ce,timingSafeEqual as Ee}from"node:crypto";import m from"node:path";import{WebSocket as ie,WebSocketServer as Re}from"ws";import{createPreviewSiteEditEngineRuntime as be,openFileHistoryStore as xe,resolveManagedPageTemplateComposition as ke,scanSiteEditCompositionsFromFiles as ve}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as _e,scanNextAppRouterRoutesFromFiles as Te}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as Oe}from"@vcp-dev/site-edit-engine/source-watcher";function J(t){let e=new Map,n=[],i=new Map,o=[],s=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(s.map((c,h)=>[c,h])),a=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(c=>({placementKey:c.placementKey,placementUnitKey:c.placementUnitKey,relation:c.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},p=t.entries.flatMap((c,h)=>c.dynamicDataSummary?[[h,c.dynamicDataSummary]]:[]),d=new Map(t.entries.map((c,h)=>[c.key,h])),l=[];for(let[c,h]of t.entries.entries()){let f=h.pageCompositionActionScope;if(!f)continue;if(f.kind!=="source-siblings"){l.push([c,{kind:f.kind}]);continue}let _=f.siblingKeys.map(R=>{let b=d.get(R);if(b===void 0)throw new Error("Render entry "+h.key+" references missing source sibling "+R);return b});l.push([c,{kind:"source-siblings",siblingEntryIndexes:_}])}let S=c=>{let h=e.get(c);if(typeof h=="number")return h;let f=n.length;return n.push(c),e.set(c,f),f},z=c=>{let h=null;for(let f of de(c)){let _=`${h??"root"}\0${f}`,R=i.get(_);if(typeof R=="number"){h=R;continue}let b=o.length;o.push([h,S(f)]),i.set(_,b),h=b}if(h===null)throw new Error("Document key must not be empty");return h};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,composition:{...t.composition},routeId:t.composition.routeId,version:t.version,pageComposition:a,tokens:n,nodes:o,entries:t.entries.map(c=>{let h=c.pageCompositionUnitKey?r.get(c.pageCompositionUnitKey)??-1:-1;if(c.pageCompositionUnitKey&&h<0)throw new Error(`Render entry ${c.key} references unknown page composition unit ${c.pageCompositionUnitKey}`);return[z(c.key),ue(c.operationSummary),...h>=0?[h]:[]]}),...l.length>0?{pageCompositionActionScopes:l}:{},...t.entries.every(c=>c.parentKey!==void 0)?{entryParentNodeIds:t.entries.map(c=>c.parentKey===null?null:z(c.parentKey))}:{},...p.length>0?{dynamicDataSummaries:p}:{}}}function ue(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function de(t){if(!t)return[];let e=[],n=0,i=L(t,n);if(i<0)return e.push(t),e;for(e.push(t.slice(0,i)),n=i;n<t.length;){if(i=L(t,n+(t.startsWith("::",n)?2:1)),i<0){e.push(t.slice(n));break}e.push(t.slice(n,i)),n=i}return e}function L(t,e){let n=t.indexOf("/",e),i=t.indexOf("::",e);return n<0?i:i<0?n:Math.min(n,i)}var Q="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",B="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var g=class t{constructor(){}static parseMessage(e,n={}){let i=JSON.parse(e);if(!t.isMessage(i,n))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(u(e)&&u(e.payload)){let n=typeof e.payload.id=="string"?e.payload.id:"unknown",i=this.isRequestKind(e.payload.kind)?e.payload.kind:"get-document";return{id:n,kind:i}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="initialize-session"||e==="get-document"||e==="get-history-status"||e==="get-style-context"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="get-operation-result"||e==="undo"||e==="redo"}static isMessage(e,n={}){return!u(e)||typeof e.type!="string"?!1:e.type==="request"?le(e.payload,n):e.type==="response"||e.type==="error"?u(e.payload)&&typeof e.payload.id=="string":e.type==="event"?u(e.payload)&&u(e.payload.event):e.type==="auth"?u(e.payload)&&typeof e.payload.key=="string":e.type==="ping"||e.type==="pong"?u(e.payload)&&typeof e.payload.id=="string":e.type==="auth-ok"&&u(e.payload)&&e.payload.ok===!0}},T=class{constructor(e){this.options=e,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new N,this.eventDispatcher=new F(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.handleEngineEvent(n)}),e.socket.onmessage=n=>{let i=pe(n.data);if(i!==null){this.sendJson({type:"pong",payload:{id:i}});return}try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(o){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:o instanceof Error?o.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe(),this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.clear()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let i=n.payload,{id:o}=i;switch(i.kind){case"initialize-session":{let s=H(i),r=await this.resolve(s),a=await this.options.engine.initializeSession({composition:r.context,...i.mutationContext?{mutationContext:i.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:i.kind,result:I({...a,data:{...a.data,document:Y(a.data.document)}},r.context)}});return}case"get-document":{let s=await this.resolveWireContext(i),r=await this.options.engine.getDocument(s);this.sendResult(o,i.kind,Se(r,s));return}case"get-history-status":{let s=await this.resolveWireContext(i),r=await this.options.engine.getHistoryStatus(s);this.sendResult(o,i.kind,I(r,s));return}case"get-style-context":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getStyleContext(s),s));return}case"get-object-edit-context":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getObjectEditContext(i.key,s,i.selectionRef),s));return}case"get-class-name-source":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getClassNameSource(i.key,s),s));return}case"get-source-tree":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getSourceTree(s),s));return}case"get-source-file":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getSourceFile({path:i.path,composition:s}),s));return}case"resolve-source-location":{let s=await this.resolveWireContext(i),r=I(await this.options.engine.resolveSourceLocation({key:i.key,composition:s}),s);this.sendResult(o,i.kind,r);return}case"execute":case"get-operation-result":{let s=await this.resolveOperationWireContext(i.request),r=await this.resolveWireContext(i);if(w(s)!==w(r))throw new C(Q,"The execute request composition does not match its operation composition.");let a=await this.normalizeOperationRequest(i.request,s);if(i.kind==="get-operation-result"){this.sendResult(o,i.kind,await this.options.engine.getOperationResult?.(a)??null);return}await this.handleBufferedOperation(o,i.kind,()=>this.options.engine.execute(a));return}case"undo":case"redo":{let s=await this.resolveWireContext(i);await this.handleBufferedOperation(o,i.kind,()=>i.kind==="undo"?this.options.engine.undo(s):this.options.engine.redo(s));return}}}catch(n){let i=g.extractRequestInfo(e);this.sendJson({type:"error",payload:{...i,message:K(n),code:D(n)}})}}async resolve(e){return this.options.engine.resolveComposition(e)}async resolveAndAssert(e){let n=await this.resolve(e),i=this.session.activeComposition;if(!i)throw new C(B,"Initialize a composition session before issuing this request.");if(w(i)!==w(n.context))throw new C(Q,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(e){let n=H(e);return this.resolveAndAssert(n)}async resolveOperationWireContext(e){let n=ye(e);return this.resolveAndAssert(n)}activateSession(e){this.session.activeComposition=e,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(w(e))}async normalizeOperationRequest(e,n){let i={...e,composition:G(n)};if(i.target.kind==="route"){let o=i.target,s=H(o),r=await this.resolveAndAssert(s);i.target={...o,composition:G(r)}}return i}handleEngineEvent(e){let n=e?.composition;if(!me(n))return;let i=w(n);this.session.subscribedCompositionKeys.has(i)&&(this.eventDispatcher.push(e),e.type==="site-edit.graph.invalidated"&&i===Ie(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(i)))}sendResult(e,n,i){this.sendJson({type:"response",payload:{id:e,kind:n,result:i}})}async handleRawMessage(e){try{await this.handleMessage(g.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let i=we(e);this.sendJson({type:"error",payload:{...i,message:K(n),code:D(n)}})}}sendJson(e){this.options.socket.send(g.stringifyMessage(e))}async handleBufferedOperation(e,n,i){this.eventDispatcher.beginBuffering();try{let o=await i();this.sendResult(e,n,o)}catch(o){this.sendJson({type:"error",payload:{id:e,kind:n,message:K(o),code:D(o)}})}finally{this.eventDispatcher.flush()}}},he=256;function pe(t){if(t.length>he||!t.includes('"ping"'))return null;try{let e=JSON.parse(t);return u(e)&&e.type==="ping"&&u(e.payload)&&typeof e.payload.id=="string"?e.payload.id:null}catch{return null}}var C=class extends Error{constructor(e,n){super(n),this.code=e,this.name="BridgeProtocolError"}},F=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let n={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(n=>this.send(n))}},N=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await e()},i=this.queue.then(n,n);return this.queue=i.then(()=>{},()=>{}),i}};function le(t,e){if(!u(t)||typeof t.id!="string"||!g.isRequestKind(t.kind))return!1;let n=x(t.composition);switch(t.kind){case"initialize-session":return n&&(t.mutationContext===void 0||M(t.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof t.key=="string"&&n&&fe(t.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof t.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof t.path=="string"&&n;case"execute":case"get-operation-result":return n&&ge(t.request)&&(t.request.mutationContext===void 0||M(t.request.mutationContext))&&(e.validateOperationRequest?.(t.request)??!0);case"undo":case"redo":return n&&t.mutationContext===void 0||n&&M(t.mutationContext)}}function fe(t){if(t===void 0)return!0;if(!u(t)||typeof t.key!="string"||t.key.length===0)return!1;let e=t.instanceIndex,n=t.instanceCount;return e!==void 0&&(typeof e!="number"||!Number.isInteger(e)||e<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof e=="number"&&typeof n=="number"&&e>=n?!1:Object.keys(t).every(i=>i==="key"||i==="instanceIndex"||i==="instanceCount")}function ge(t){return u(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&x(t.composition)&&typeof t.kind=="string"&&u(t.target)&&(t.target.kind!=="route"||x(t.target.composition))&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&u(t.params)}function x(t){return u(t)&&typeof t.routeId=="string"&&t.routeId.length>0&&(t.templateId===void 0||typeof t.templateId=="string"&&t.templateId.length>0)&&Object.keys(t).every(e=>e==="routeId"||e==="templateId")}function me(t){return u(t)&&typeof t.routeId=="string"&&(t.effectiveTemplateId===void 0||typeof t.effectiveTemplateId=="string")}function H(t){if(x(t.composition))return t.composition;throw new C(B,"A composition selector is required for this request.")}function ye(t){if(u(t)&&x(t.composition))return t.composition;throw new C(B,"A composition selector is required for execute.")}function G(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function Ie(t){return t.activeComposition?w(t.activeComposition):null}function w(t){return JSON.stringify([t.routeId,t.effectiveTemplateId??null])}function I(t,e){return u(t)&&t.ok===!0?{...t,composition:e}:t}function Se(t,e){return!u(t)||t.ok!==!0?t:{...t,...e?{composition:e}:{},data:Y(t.data)}}function Y(t){let e=t;return J(e.routeId?e:{...e,routeId:e.composition.routeId})}function u(t){return typeof t=="object"&&t!==null}function M(t){return u(t)&&typeof t.editingSessionId=="string"&&t.editingSessionId.length>0&&typeof t.lockId=="string"&&t.lockId.length>0&&typeof t.ownerId=="string"&&t.ownerId.length>0&&typeof t.leaseAssertion=="string"&&t.leaseAssertion.length>0}function D(t){if(!(!u(t)||typeof t.code!="string"||t.code.length===0))return t.code}function K(t){return t instanceof Error||u(t)&&typeof t.message=="string"&&t.message.length>0?t.message:"Unknown websocket operation failure"}function we(t){try{return g.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var O=class{constructor(){this.tail=Promise.resolve()}run(e){let n=this.tail.then(e,e);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as Ae,computeSiteEditCompositionKey as E}from"@vcp-dev/contracts";var He="127.0.0.1",Me=30218,De="/__vcp/site-edit/bridge",se=6*1024*1024,Ke=se*2,Fe=3e3,Z=50,U=".vcp/current-snapshot.json",X="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function ht(t){let e=A(t),n=re(e),i=oe(),o=i.get(n);if(o)return o;let s=new v(e);i.set(n,s);try{s.start()}catch(r){throw i.delete(n),s.stop(),r}return s}function pt(t,e,n={}){let i=new v(A(e),t,!1,n);return i.start({listen:!1}),i}var v=class{constructor(e,n=ne.createServer((s,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),i=!0,o={}){this.options=e,this.server=n,this.closeHttpServerOnStop=i,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new O,this.operationReceipts=new Map,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.launchCandidateEntries=null,this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.stopping=!1,this.runtimeVersionFloors=new Map,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve(),this.bridgeEngine=null,this.lastNotifiedHealth=null}start(e={}){if(!this.isStarted){this.stopping=!1;try{k.mkdirSync(m.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(n){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${n instanceof Error?n.message:String(n)}`)}if(this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime(),this.bridgeEngine=this.createBridgeEngine(),e.webSocket!==!1){let n=Ue({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.bridgeEngine});this.stopWebSocketServer=n.stop}this.stopWatching=new Oe({projectRoot:this.options.projectRoot,watch_dirs:Ne(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??Z,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.notifyHealthChanged()}}getEngine(){if(!this.bridgeEngine)throw new Error("VCP site edit preview bridge runtime is not ready");return this.bridgeEngine}getHealth(){let e=!!this.runtime&&!this.compositionResolutionError;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopping=!0,this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.closeHttpServerOnStop&&this.server.close();let e=oe(),n=re(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1,this.notifyHealthChanged()}notifyHealthChanged(){let e=this.options.onHealthChanged;if(!e)return;let n=this.getHealth(),i=JSON.stringify(n);if(i!==this.lastNotifiedHealth){this.lastNotifiedHealth=i;try{e(n)}catch(o){console.warn(`[vcp-site-edit] onHealthChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}}createRuntime(e,n=null){let i=this.runtime;this.activeHistoryStore=e??null;let o=(this.dependencies.createRuntime??be)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:s=>{"version"in s&&typeof s.version=="number"&&this.recordRuntimeVersion(s.version,"composition"in s?s.composition:void 0);for(let r of this.eventListeners)r(s)},...e?{historyStore:e}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=n,i?.dispose(),this.notifyHealthChanged()}createBridgeEngine(){return{resolveComposition:e=>this.resolveComposition(e),initializeSession:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)throw new Error(`${n.code}: ${n.message}`);let i=Pe(e.composition),o=this.getRuntime(),s=y(i);await o.initializeComposition(s);let a=o.getDocument(s).data;await this.recordRuntimeVersion(a.version,i);let p=(await o.getHistoryStatus()).data;return{ok:!0,composition:i,data:{document:a,historyStatus:p}}}),getDocument:async e=>{let n=this.getRuntime(),i=y(e);await n.initializeComposition(i);let o=n.getDocument(i);return await this.recordRuntimeVersion(o.data.version,e),{...o,composition:e}},getHistoryStatus:async e=>({...await this.getRuntime().getHistoryStatus(),composition:e}),getStyleContext:async e=>this.getRuntime().getStyleContext(y(e)),getObjectEditContext:async(e,n,i)=>this.getRuntime().getObjectEditContext(e,y(n),i),getClassNameSource:async(e,n)=>this.getRuntime().getClassNameSource(e,y(n)),getSourceTree:async e=>this.getRuntime().getSourceTree(y(e)),getSourceFile:async e=>this.getRuntime().getSourceFile({path:e.path,composition:y(e.composition)}),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,composition:y(e.composition)}),execute:(e,n)=>this.executeWithReceipt(e,()=>this.mutationQueue.run(async()=>{let i=await this.bindHistorySession(this.options.snapshotId);if(!i.ok)return P(i,e);let o=await this.getRuntime().executeOperation(e);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o})),getOperationResult:e=>{if(e.siteId!==this.options.siteId||e.baseSnapshotId!==this.options.snapshotId)return null;let n=this.operationReceipts.get(this.operationReceiptKey(e));return n?.fingerprint===this.operationFingerprint(e)?n.result:null},undo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return P(n,void 0,e);let i=await this.getRuntime().undo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??e),i.composition?i:{...i,composition:e}}),redo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return P(n,void 0,e);let i=await this.getRuntime().redo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??e),i.composition?i:{...i,composition:e}}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}operationReceiptKey(e){return JSON.stringify([e.siteId,e.baseSnapshotId,e.id])}operationFingerprint(e){return j("sha256").update(JSON.stringify(e)).digest("hex")}executeWithReceipt(e,n){if(e.siteId!==this.options.siteId||e.baseSnapshotId!==this.options.snapshotId)return n();let i=this.operationReceiptKey(e),o=this.operationFingerprint(e),s=this.operationReceipts.get(i);if(s)return s.fingerprint!==o?Promise.reject(new Error("Operation ID was reused with a different request.")):s.result;let r={fingerprint:o,result:n(),settled:!1};return this.operationReceipts.set(i,r),r.result.then(()=>{r.settled=!0;for(let[a,p]of this.operationReceipts){if(this.operationReceipts.size<=256)break;p.settled&&this.operationReceipts.delete(a)}},()=>{this.operationReceipts.delete(i)}),r.result}async bindHistorySession(e){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===e)return{ok:!0};let n=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===e)return;let i=await(this.dependencies.openHistoryStore??xe)(Ve({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let o=i.readDocumentVersion?await i.readDocumentVersion():0,s=this.compositions[0]?.context;if(s){let r=E(s);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(i,e)});this.historySessionTransition=n.catch(()=>{});try{return await n,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(e,n){let i=n?E(n):null;i&&this.runtimeVersionFloors.set(i,Math.max(this.runtimeVersionFloors.get(i)??0,e));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let s=i?this.runtimeVersionFloors.get(i)??e:e;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(s)).catch(()=>{}),this.versionPersistence}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let i of e)this.pendingChangedFiles.add(i);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??Z;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((n,i)=>n.localeCompare(i));if(this.pendingChangedFiles.clear(),e.some(Qe)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let n of e)this.runtime?.notifyFileChanged(n)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let e=this.readCurrentSnapshotMarker();!e||e===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(e)).catch(n=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${n instanceof Error?n.message:String(n)}`)})}readCurrentSnapshotMarker(){try{let e=k.readFileSync(m.join(this.options.projectRoot,U),"utf8"),n=JSON.parse(e),i=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!i||q(i)!==i?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):i}catch(e){return console.warn(`[vcp-site-edit] current-snapshot marker unreadable: ${e instanceof Error?e.message:String(e)}`),null}}async advanceSnapshotId(e){if(this.stopping||!this.isStarted||e===this.options.snapshotId)return;let n=this.options.snapshotId;this.options.snapshotId=e;try{if(this.options.historyRoot){let o=await this.bindHistorySession(e);if(!o.ok)throw new Error(`${o.code}: ${o.message}`)}else this.createRuntime()}catch(o){this.options.snapshotId=n,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${n}: ${o instanceof Error?o.message:String(o)}`);return}let i=new Date().toISOString();for(let o of this.compositions){let s={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:e,previousSnapshotId:n,at:i};for(let r of this.eventListeners)try{r(s)}catch(a){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${a instanceof Error?a.message:String(a)}`)}}try{this.options.onSnapshotIdChanged?.(e,n)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(e=!1){try{let n=Be(this.options.projectRoot),i=Te(n),o=i.length>0?i:(this.options.runtime.routes??[]).map(d=>({...d})),s=ve({files:n});if(s.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=JSON.stringify(s.filter(d=>d.context.effectiveTemplateId!==void 0).map(d=>d.entryFile).sort());if(e&&(this.launchCandidateEntries=r),this.launchCandidateEntries!==r)throw new Error("SITE_EDIT_RUNTIME_RELOAD_REQUIRED: Candidate route collection changed since preview launch");let a=new Map(this.compositions.map(d=>[E(d.context),d])),p=new Map(s.map(d=>[E(d.context),d]));if(this.runtime){for(let[d,l]of a){let S=p.get(d);(!S||S.entryFile!==l.entryFile)&&this.runtime.unregisterComposition(y(l.context))}for(let[d,l]of p){let S=a.get(d);(!S||S.entryFile!==l.entryFile)&&this.runtime.registerComposition(l.context,l.entryFile)}}return this.routes=o,this.compositions=s,this.compositionFiles=n,this.compositionResolutionError=null,this.notifyHealthChanged(),!0}catch(n){let i=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=i,e)throw i;return this.notifyHealthChanged(),!1}}resolveComposition(e){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=_e(e.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let i=this.routes.find(r=>r.routeId===n);if(!i)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let o=ke({selector:{routeId:n,...e.templateId!==void 0?{templateId:e.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:i.entryFile}),s=E(o.context);if(!this.compositions.some(r=>E(r.context)===s))throw new Error(`Composition is not registered by the preview bridge: ${s}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(e=>{let n=this.runtimeVersionFloors.get(E(e.context));return n===void 0?[]:[{composition:e.context,version:n}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function lt(t){let e=new v(A(t),ne.createServer(),!1);return e.start({listen:!1,webSocket:!1}),{engine:e.getEngine(),getHealth:()=>e.getHealth(),stop:()=>e.stop()}}function ft(t){return A(t)}function A(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:m.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??He,port:t.port??Me,path:t.path??De,sessionKey:t.sessionKey??Je(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{},onSnapshotIdChanged:t.onSnapshotIdChanged,onHealthChanged:t.onHealthChanged}}function Ne(t){let e=["app","src/app","components/site","src/components/site","lib/generated","src/lib/generated","public"];return[...new Set([...t?.length?t:e,".vcp"])]}function Be(t){let e=[],n=["app","src/app",".vcp"],i=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=s=>{if(!k.existsSync(s))return;let r;try{r=k.readdirSync(s,{withFileTypes:!0})}catch{return}for(let a of r){if(a.name.startsWith(".")&&a.name!==".vcp")continue;if(a.isDirectory()){if(i.has(a.name))continue;o(m.join(s,a.name));continue}if(!a.isFile())continue;let p=m.join(s,a.name),d="";try{d=k.readFileSync(p,"utf8")}catch{}e.push({path:m.relative(t,p).replaceAll(m.sep,"/"),content:d})}};for(let s of n)o(m.join(t,s));return e.sort((s,r)=>s.path.localeCompare(r.path))}function Pe(t){if(!t||typeof t!="object"||typeof t.routeId!="string")throw new Error("Normalized composition context is required.");if("effectiveTemplateId"in t)return t;let e=t;return{routeId:e.routeId,...e.templateId?{effectiveTemplateId:e.templateId}:{}}}function y(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function Ve(t){return m.join(m.resolve(t.root),q(t.siteId),q(t.snapshotId),j("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function q(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:j("sha256").update(t).digest("hex").slice(0,32)}function oe(){let t=globalThis;return t[X]??=new Map,t[X]}function re(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function P(t,e,n){let i=n??(e?.composition?{routeId:e.composition.routeId,...e.composition.templateId?{effectiveTemplateId:e.composition.templateId}:{}}:void 0);if(!i)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:i,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",composition:i,target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function Ue(t){let e=new Re({maxPayload:se,noServer:!0}),n=new WeakSet,i=s=>{new T({socket:new W(s),engine:t.engine,validateOperationRequest:r=>Ae.safeParse(r).success})},o=(s,r,a)=>{if(!qe(s,t))return;let p=We(s,t);if(!p){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),r.destroy();return}p.authenticatedByHeader&&n.add(s),e.handleUpgrade(s,r,a,
|
|
4
|
+
`),r.destroy();return}p.authenticatedByHeader&&n.add(s),e.handleUpgrade(s,r,a,d=>{e.emit("connection",d,s)})};return t.server.on("upgrade",o),e.on("connection",(s,r)=>{if(n.has(r)){i(s);return}$e(s,t.sessionKey,()=>{i(s)})}),e.stop=()=>{t.server.off("upgrade",o);for(let s of e.clients)s.close(1001,"bridge stopped");e.close()},e}var W=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("error",()=>{}),e.on("message",n=>{this.onmessage?.({data:n.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){if(this.socket.readyState===ie.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>Ke){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,n){this.socket.close(e,n)}};function qe(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function We(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let i=V(t.headers["x-vcp-site-edit-key"]),o=ae(i,e.sessionKey),s=V(t.headers.host),r=V(t.headers.origin);return s&&!je(s,e.host,r)?null:r?Le(r,s,e.allowedOrigins)?{authenticatedByHeader:o}:null:o?{authenticatedByHeader:!0}:null}function $e(t,e,n){let i=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},Fe),o,s=()=>{clearTimeout(i),t.off("message",o),t.off("close",s)},r=()=>{s(),t.close(1008,"bridge authentication failed")};o=a=>{try{let p=g.parseMessage(a.toString());if(p.type!=="auth"||!ae(p.payload.key,e)){r();return}s(),t.readyState===ie.OPEN&&t.send(g.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{r()}},t.on("message",o),t.on("close",s)}function ae(t,e){if(!t)return!1;let n=Buffer.from(t),i=Buffer.from(e);return n.length===i.length&&Ee(n,i)}function je(t,e,n){let i=te(t),o=te(e);return i===o||ee(o)&&ee(i)&&(!n||$(n,t))||ze(o)&&(!n||$(n,t))}function ze(t){return t==="0.0.0.0"||t==="::"}function ee(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function Le(t,e,n){return n.includes(t)||(e?$(t,e):!1)}function $(t,e){try{return new URL(t).host===ce(e)}catch{return!1}}function te(t){let e=ce(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function ce(t){return t.trim().toLowerCase()}function V(t){return Array.isArray(t)?t[0]:t}function Je(){return Ce(32).toString("base64url")}function Qe(t){let e=t.replaceAll("\\","/");return e===U||e.endsWith(`/${U}`)}export{Ke as SITE_EDIT_BRIDGE_MAX_BUFFERED_BYTES,se as SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES,v as SiteEditBridgeServer,pt as attachSiteEditBridgeServer,Ue as attachSiteEditWebSocketServer,lt as createSiteEditBridgeEngineHost,ht as ensureSiteEditBridgeServerStarted,ft as normalizeSiteEditBridgeServerOptions};
|
package/dist/window-message.d.ts
CHANGED
|
@@ -128,6 +128,8 @@ export type SiteEditFrameRuntimeBridgeStatus = {
|
|
|
128
128
|
attempt?: number;
|
|
129
129
|
retryInMs?: number;
|
|
130
130
|
messageKey?: string;
|
|
131
|
+
/** 失败或重连的机器可读原因(如 SITE_EDIT_BRIDGE_CONNECT_TIMEOUT),不含错误正文。 */
|
|
132
|
+
failureCode?: string;
|
|
131
133
|
};
|
|
132
134
|
export type SiteEditFrameRuntimeRequestBody = {
|
|
133
135
|
type: "request";
|