@vcp-dev/site-edit-bridge 0.9.6 → 0.10.0
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/dist/auto-bootstrap.js +4 -4
- package/dist/client.d.ts +59 -5
- package/dist/client.js +1 -1
- package/dist/index.js +15 -15
- package/dist/next-dev-server.js +3 -3
- package/dist/property-preview.d.ts +26 -0
- package/dist/protocol.d.ts +8 -3
- package/dist/protocol.js +29 -1
- package/dist/server.js +2 -2
- package/dist/window-message.d.ts +12 -1
- package/dist/window-message.js +1 -1
- package/package.json +3 -3
package/dist/next-dev-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import ut from"node:http";import{existsSync as _,readFileSync as b}from"node:fs";import{randomUUID as Ae}from"node:crypto";import{createRequire as Bt}from"node:module";import{Socket as Pt}from"node:net";import H from"node:path";import{basename as Dt,dirname as A,join as B}from"node:path";import{fileURLToPath as Ot}from"node:url";import Xt from"node:http";import P from"node:fs";import{createHash as Rt,randomBytes as te,timingSafeEqual as ee}from"node:crypto";import g from"node:path";import{WebSocket as Et,WebSocketServer as ne}from"ws";import{createPreviewSiteEditEngineRuntime as se,openFileHistoryStore as oe,resolveManagedPageTemplateComposition as ie,scanSiteEditCompositionsFromFiles as re}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as ae,scanNextAppRouterRoutesFromFiles as ce}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as ue}from"@vcp-dev/site-edit-engine/source-watcher";function gt(t){let e=new Map,n=[],s=new Map,o=[],i=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(i.map((d,p)=>[d,p])),a=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(d=>({placementKey:d.placementKey,placementUnitKey:d.placementUnitKey,relation:d.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},u=t.entries.flatMap((d,p)=>d.dynamicDataSummary?[[p,d.dynamicDataSummary]]:[]),c=d=>{let p=e.get(d);if(typeof p=="number")return p;let l=n.length;return n.push(d),e.set(d,l),l},h=d=>{let p=null;for(let l of Wt(d)){let S=`${p??"root"}\0${l}`,I=s.get(S);if(typeof I=="number"){p=I;continue}let w=o.length;o.push([p,c(l)]),s.set(S,w),p=w}if(p===null)throw new Error("Document key must not be empty");return p};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(d=>{let p=d.pageCompositionUnitKey?r.get(d.pageCompositionUnitKey)??-1:-1;if(d.pageCompositionUnitKey&&p<0)throw new Error(`Render entry ${d.key} references unknown page composition unit ${d.pageCompositionUnitKey}`);return[h(d.key),$t(d.operationSummary),...p>=0?[p]:[]]}),...u.length>0?{dynamicDataSummaries:u}:{}}}function $t(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function Wt(t){if(!t)return[];let e=[],n=0,s=mt(t,n);if(s<0)return e.push(t),e;for(e.push(t.slice(0,s)),n=s;n<t.length;){if(s=mt(t,n+(t.startsWith("::",n)?2:1)),s<0){e.push(t.slice(n));break}e.push(t.slice(n,s)),n=s}return e}function mt(t,e){let n=t.indexOf("/",e),s=t.indexOf("::",e);return n<0?s:s<0?n:Math.min(n,s)}var yt="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",Z="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var m=class t{constructor(){}static parseMessage(e,n={}){let s=JSON.parse(e);if(!t.isMessage(s,n))throw new Error("Invalid site edit bridge message");return s}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(f(e)&&f(e.payload)){let n=typeof e.payload.id=="string"?e.payload.id:"unknown",s=this.isRequestKind(e.payload.kind)?e.payload.kind:"get-document";return{id:n,kind:s}}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==="undo"||e==="redo"}static isMessage(e,n={}){return!f(e)||typeof e.type!="string"?!1:e.type==="request"?Lt(e.payload,n):e.type==="response"||e.type==="error"?f(e.payload)&&typeof e.payload.id=="string":e.type==="event"?f(e.payload)&&f(e.payload.event):e.type==="auth"?f(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"&&f(e.payload)&&e.payload.ok===!0}},j=class{constructor(e){this.options=e,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new Y,this.eventDispatcher=new Q(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(s){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:s instanceof Error?s.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 s=n.payload,{id:o}=s;switch(s.kind){case"initialize-session":{let i=L(s),r=await this.resolve(i),a=await this.options.engine.initializeSession({composition:r.context,...s.mutationContext?{mutationContext:s.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:s.kind,result:x({...a,data:{...a.data,document:It(a.data.document)}},r.context)}});return}case"get-document":{let i=await this.resolveWireContext(s),r=await this.options.engine.getDocument(i);this.sendResult(o,s.kind,Yt(r,i));return}case"get-history-status":{let i=await this.resolveWireContext(s),r=await this.options.engine.getHistoryStatus(i);this.sendResult(o,s.kind,x(r,i));return}case"get-style-context":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getStyleContext(i),i));return}case"get-object-edit-context":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getObjectEditContext(s.key,i),i));return}case"get-class-name-source":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getClassNameSource(s.key,i),i));return}case"get-source-tree":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getSourceTree(i),i));return}case"get-source-file":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getSourceFile({path:s.path,composition:i}),i));return}case"resolve-source-location":{let i=await this.resolveWireContext(s),r=x(await this.options.engine.resolveSourceLocation({key:s.key,composition:i}),i);this.sendResult(o,s.kind,r);return}case"execute":{let i=await this.resolveOperationWireContext(s.request),r=await this.resolveWireContext(s);if(C(i)!==C(r))throw new v(yt,"The execute request composition does not match its operation composition.");let a=await this.normalizeOperationRequest(s.request,i);await this.handleBufferedOperation(o,s.kind,()=>this.options.engine.execute(a));return}case"undo":case"redo":{let i=await this.resolveWireContext(s);await this.handleBufferedOperation(o,s.kind,()=>s.kind==="undo"?this.options.engine.undo(i):this.options.engine.redo(i));return}}}catch(n){let s=m.extractRequestInfo(e);this.sendJson({type:"error",payload:{...s,message:G(n),code:J(n)}})}}async resolve(e){return this.options.engine.resolveComposition(e)}async resolveAndAssert(e){let n=await this.resolve(e),s=this.session.activeComposition;if(!s)throw new v(Z,"Initialize a composition session before issuing this request.");if(C(s)!==C(n.context))throw new v(yt,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(e){let n=L(e);return this.resolveAndAssert(n)}async resolveOperationWireContext(e){let n=Gt(e);return this.resolveAndAssert(n)}activateSession(e){this.session.activeComposition=e,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(C(e))}async normalizeOperationRequest(e,n){let s={...e,composition:St(n)};if(s.target.kind==="route"){let o=s.target,i=L(o),r=await this.resolveAndAssert(i);s.target={...o,composition:St(r)}}return s}handleEngineEvent(e){let n=e?.composition;if(!Jt(n))return;let s=C(n);this.session.subscribedCompositionKeys.has(s)&&(this.eventDispatcher.push(e),e.type==="site-edit.graph.invalidated"&&s===Qt(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(s)))}sendResult(e,n,s){this.sendJson({type:"response",payload:{id:e,kind:n,result:s}})}async handleRawMessage(e){try{await this.handleMessage(m.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let s=Zt(e);this.sendJson({type:"error",payload:{...s,message:G(n),code:J(n)}})}}sendJson(e){this.options.socket.send(m.stringifyMessage(e))}async handleBufferedOperation(e,n,s){this.eventDispatcher.beginBuffering();try{let o=await s();this.sendResult(e,n,o)}catch(o){this.sendJson({type:"error",payload:{id:e,kind:n,message:G(o),code:J(o)}})}finally{this.eventDispatcher.flush()}}},v=class extends Error{constructor(e,n){super(n),this.code=e,this.name="BridgeProtocolError"}},Q=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))}},Y=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()},s=this.queue.then(n,n);return this.queue=s.then(()=>{},()=>{}),s}};function Lt(t,e){if(!f(t)||typeof t.id!="string"||!m.isRequestKind(t.kind))return!1;let n=k(t.composition);switch(t.kind){case"initialize-session":return n&&(t.mutationContext===void 0||z(t.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":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":return n&&zt(t.request)&&(t.request.mutationContext===void 0||z(t.request.mutationContext))&&(e.validateOperationRequest?.(t.request)??!0);case"undo":case"redo":return n&&t.mutationContext===void 0||n&&z(t.mutationContext)}}function zt(t){return f(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&k(t.composition)&&typeof t.kind=="string"&&f(t.target)&&(t.target.kind!=="route"||k(t.target.composition))&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&f(t.params)}function k(t){return f(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 Jt(t){return f(t)&&typeof t.routeId=="string"&&(t.effectiveTemplateId===void 0||typeof t.effectiveTemplateId=="string")}function L(t){if(k(t.composition))return t.composition;throw new v(Z,"A composition selector is required for this request.")}function Gt(t){if(f(t)&&k(t.composition))return t.composition;throw new v(Z,"A composition selector is required for execute.")}function St(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function Qt(t){return t.activeComposition?C(t.activeComposition):null}function C(t){return JSON.stringify([t.routeId,t.effectiveTemplateId??null])}function x(t,e){return f(t)&&t.ok===!0?{...t,composition:e}:t}function Yt(t,e){return!f(t)||t.ok!==!0?t:{...t,...e?{composition:e}:{},data:It(t.data)}}function It(t){let e=t;return gt(e.routeId?e:{...e,routeId:e.composition.routeId})}function f(t){return typeof t=="object"&&t!==null}function z(t){return f(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 J(t){if(!(!f(t)||typeof t.code!="string"||t.code.length===0))return t.code}function G(t){return t instanceof Error||f(t)&&typeof t.message=="string"&&t.message.length>0?t.message:"Unknown websocket operation failure"}function Zt(t){try{return m.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var F=class{constructor(){this.tail=Promise.resolve()}run(e){let n=this.tail.then(e,e);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as de,computeSiteEditCompositionKey as R}from"@vcp-dev/contracts";var he="127.0.0.1",pe=30218,fe="/__vcp/site-edit/bridge",bt=6*1024*1024,le=bt*2,me=3e3,wt=50,et=".vcp/current-snapshot.json",xt="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function _t(t,e,n={}){let s=new nt(ge(e),t,!1,n);return s.start({listen:!1}),s}var nt=class{constructor(e,n=Xt.createServer((i,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),s=!0,o={}){this.options=e,this.server=n,this.closeHttpServerOnStop=s,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new F,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=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()}start(e={}){if(this.isStarted)return;this.stopping=!1;try{P.mkdirSync(g.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(s){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${s instanceof Error?s.message:String(s)}`)}this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime();let n=ve({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=n.stop,this.stopWatching=new ue({projectRoot:this.options.projectRoot,watch_dirs:ye(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??wt,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:s=>this.handleFileChanged(s),on_batch_change:s=>this.handleFilesChanged(s)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host)}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=xe(),n=Ce(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1}createRuntime(e,n=null){let s=this.runtime;this.activeHistoryStore=e??null;let o=(this.dependencies.createRuntime??se)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:i=>{"version"in i&&typeof i.version=="number"&&this.recordRuntimeVersion(i.version,"composition"in i?i.composition:void 0);for(let r of this.eventListeners)r(i)},...e?{historyStore:e}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=n,s?.dispose()}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 s=Ie(e.composition),o=this.getRuntime(),i=y(s);await o.initializeComposition(i);let a=o.getDocument(i).data;await this.recordRuntimeVersion(a.version,s);let u=(await o.getHistoryStatus()).data;return{ok:!0,composition:s,data:{document:a,historyStatus:u}}}),getDocument:async e=>{let n=this.getRuntime(),s=y(e);await n.initializeComposition(s);let o=n.getDocument(s);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)=>this.getRuntime().getObjectEditContext(e,y(n)),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.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return X(s,e);let o=await this.getRuntime().executeOperation(e);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o}),undo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return X(n,void 0,e);let s=await this.getRuntime().undo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition??e),s.composition?s:{...s,composition:e}}),redo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return X(n,void 0,e);let s=await this.getRuntime().redo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition??e),s.composition?s:{...s,composition:e}}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}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 s=await(this.dependencies.openHistoryStore??oe)(we({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let o=s.readDocumentVersion?await s.readDocumentVersion():0,i=this.compositions[0]?.context;if(i){let r=R(i);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(s,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 s=n?R(n):null;s&&this.runtimeVersionFloors.set(s,Math.max(this.runtimeVersionFloors.get(s)??0,e));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let i=s?this.runtimeVersionFloors.get(s)??e:e;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(i)).catch(()=>{}),this.versionPersistence}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let s of e)this.pendingChangedFiles.add(s);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??wt;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,s)=>n.localeCompare(s));if(this.pendingChangedFiles.clear(),e.some(He)&&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=P.readFileSync(g.join(this.options.projectRoot,et),"utf8"),n=JSON.parse(e),s=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!s||st(s)!==s?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):s}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 s=new Date().toISOString();for(let o of this.compositions){let i={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:e,previousSnapshotId:n,at:s};for(let r of this.eventListeners)try{r(i)}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=Se(this.options.projectRoot),s=ce(n),o=s.length>0?s:(this.options.runtime.routes??[]).map(u=>({...u})),i=re({files:n});if(i.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=new Map(this.compositions.map(u=>[R(u.context),u])),a=new Map(i.map(u=>[R(u.context),u]));if(this.runtime){for(let[u,c]of r){let h=a.get(u);(!h||h.entryFile!==c.entryFile)&&this.runtime.unregisterComposition(y(c.context))}for(let[u,c]of a){let h=r.get(u);(!h||h.entryFile!==c.entryFile)&&this.runtime.registerComposition(c.context,c.entryFile)}}return this.routes=o,this.compositions=i,this.compositionFiles=n,this.compositionResolutionError=null,!0}catch(n){let s=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=s,e)throw s;return!1}}resolveComposition(e){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=ae(e.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let s=this.routes.find(r=>r.routeId===n);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let o=ie({selector:{routeId:n,...e.templateId?{templateId:e.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:s.entryFile}),i=R(o.context);if(!this.compositions.some(r=>R(r.context)===i))throw new Error(`Composition is not registered by the preview bridge: ${i}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(e=>{let n=this.runtimeVersionFloors.get(R(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 ge(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:g.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??he,port:t.port??pe,path:t.path??fe,sessionKey:t.sessionKey??Pe(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{},onSnapshotIdChanged:t.onSnapshotIdChanged}}function ye(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 Se(t){let e=[],n=["app","src/app",".vcp"],s=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=i=>{if(!P.existsSync(i))return;let r;try{r=P.readdirSync(i,{withFileTypes:!0})}catch{return}for(let a of r){if(a.name.startsWith(".")&&a.name!==".vcp")continue;if(a.isDirectory()){if(s.has(a.name))continue;o(g.join(i,a.name));continue}if(!a.isFile())continue;let u=g.join(i,a.name),c="";try{c=P.readFileSync(u,"utf8")}catch{}e.push({path:g.relative(t,u).replaceAll(g.sep,"/"),content:c})}};for(let i of n)o(g.join(t,i));return e.sort((i,r)=>i.path.localeCompare(r.path))}function Ie(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 we(t){return g.join(g.resolve(t.root),st(t.siteId),st(t.snapshotId),Rt("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function st(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:Rt("sha256").update(t).digest("hex").slice(0,32)}function xe(){let t=globalThis;return t[xt]??=new Map,t[xt]}function Ce(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function X(t,e,n){let s=n??(e?.composition?{routeId:e.composition.routeId,...e.composition.templateId?{effectiveTemplateId:e.composition.templateId}:{}}:void 0);if(!s)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:s,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",composition:s,target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function ve(t){let e=new ne({maxPayload:bt,noServer:!0}),n=new WeakSet,s=i=>{new j({socket:new ot(i),engine:t.engine,validateOperationRequest:r=>de.safeParse(r).success})},o=(i,r,a)=>{if(!Re(i,t))return;let u=Ee(i,t);if(!u){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import ue from"node:http";import{existsSync as _,readFileSync as E}from"node:fs";import{randomUUID as Bt}from"node:crypto";import{createRequire as Be}from"node:module";import{Socket as Pe}from"node:net";import H from"node:path";import{basename as De,dirname as A,join as B}from"node:path";import{fileURLToPath as Oe}from"node:url";import et from"node:http";import P from"node:fs";import{createHash as be,randomBytes as tt,timingSafeEqual as nt}from"node:crypto";import g from"node:path";import{WebSocket as ve,WebSocketServer as st}from"ws";import{createPreviewSiteEditEngineRuntime as ot,openFileHistoryStore as it,resolveManagedPageTemplateComposition as rt,scanSiteEditCompositionsFromFiles as at}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as ct,scanNextAppRouterRoutesFromFiles as ut}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as dt}from"@vcp-dev/site-edit-engine/source-watcher";function ge(e){let t=new Map,n=[],s=new Map,o=[],i=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],r=new Map(i.map((d,p)=>[d,p])),a=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},u=e.entries.flatMap((d,p)=>d.dynamicDataSummary?[[p,d.dynamicDataSummary]]:[]),c=d=>{let p=t.get(d);if(typeof p=="number")return p;let l=n.length;return n.push(d),t.set(d,l),l},h=d=>{let p=null;for(let l of We(d)){let S=`${p??"root"}\0${l}`,I=s.get(S);if(typeof I=="number"){p=I;continue}let w=o.length;o.push([p,c(l)]),s.set(S,w),p=w}if(p===null)throw new Error("Document key must not be empty");return p};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:a,tokens:n,nodes:o,entries:e.entries.map(d=>{let p=d.pageCompositionUnitKey?r.get(d.pageCompositionUnitKey)??-1:-1;if(d.pageCompositionUnitKey&&p<0)throw new Error(`Render entry ${d.key} references unknown page composition unit ${d.pageCompositionUnitKey}`);return[h(d.key),$e(d.operationSummary),...p>=0?[p]:[]]}),...u.length>0?{dynamicDataSummaries:u}:{}}}function $e(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function We(e){if(!e)return[];let t=[],n=0,s=me(e,n);if(s<0)return t.push(e),t;for(t.push(e.slice(0,s)),n=s;n<e.length;){if(s=me(e,n+(e.startsWith("::",n)?2:1)),s<0){t.push(e.slice(n));break}t.push(e.slice(n,s)),n=s}return t}function me(e,t){let n=e.indexOf("/",t),s=e.indexOf("::",t);return n<0?s:s<0?n:Math.min(n,s)}var ye="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",Z="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var m=class e{constructor(){}static parseMessage(t,n={}){let s=JSON.parse(t);if(!e.isMessage(s,n))throw new Error("Invalid site edit bridge message");return s}static stringifyMessage(t){return JSON.stringify(t)}static extractRequestInfo(t){if(f(t)&&f(t.payload)){let n=typeof t.payload.id=="string"?t.payload.id:"unknown",s=this.isRequestKind(t.payload.kind)?t.payload.kind:"get-document";return{id:n,kind:s}}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==="undo"||t==="redo"}static isMessage(t,n={}){return!f(t)||typeof t.type!="string"?!1:t.type==="request"?Le(t.payload,n):t.type==="response"||t.type==="error"?f(t.payload)&&typeof t.payload.id=="string":t.type==="event"?f(t.payload)&&f(t.payload.event):t.type==="auth"?f(t.payload)&&typeof t.payload.key=="string":t.type==="auth-ok"&&f(t.payload)&&t.payload.ok===!0}},j=class{constructor(t){this.options=t,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new Y,this.eventDispatcher=new Q(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(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 s=n.payload,{id:o}=s;switch(s.kind){case"initialize-session":{let i=L(s),r=await this.resolve(i),a=await this.options.engine.initializeSession({composition:r.context,...s.mutationContext?{mutationContext:s.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:s.kind,result:x({...a,data:{...a.data,document:Ie(a.data.document)}},r.context)}});return}case"get-document":{let i=await this.resolveWireContext(s),r=await this.options.engine.getDocument(i);this.sendResult(o,s.kind,Ze(r,i));return}case"get-history-status":{let i=await this.resolveWireContext(s),r=await this.options.engine.getHistoryStatus(i);this.sendResult(o,s.kind,x(r,i));return}case"get-style-context":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getStyleContext(i),i));return}case"get-object-edit-context":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getObjectEditContext(s.key,i,s.selectionRef),i));return}case"get-class-name-source":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getClassNameSource(s.key,i),i));return}case"get-source-tree":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getSourceTree(i),i));return}case"get-source-file":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,x(await this.options.engine.getSourceFile({path:s.path,composition:i}),i));return}case"resolve-source-location":{let i=await this.resolveWireContext(s),r=x(await this.options.engine.resolveSourceLocation({key:s.key,composition:i}),i);this.sendResult(o,s.kind,r);return}case"execute":{let i=await this.resolveOperationWireContext(s.request),r=await this.resolveWireContext(s);if(C(i)!==C(r))throw new R(ye,"The execute request composition does not match its operation composition.");let a=await this.normalizeOperationRequest(s.request,i);await this.handleBufferedOperation(o,s.kind,()=>this.options.engine.execute(a));return}case"undo":case"redo":{let i=await this.resolveWireContext(s);await this.handleBufferedOperation(o,s.kind,()=>s.kind==="undo"?this.options.engine.undo(i):this.options.engine.redo(i));return}}}catch(n){let s=m.extractRequestInfo(t);this.sendJson({type:"error",payload:{...s,message:G(n),code:J(n)}})}}async resolve(t){return this.options.engine.resolveComposition(t)}async resolveAndAssert(t){let n=await this.resolve(t),s=this.session.activeComposition;if(!s)throw new R(Z,"Initialize a composition session before issuing this request.");if(C(s)!==C(n.context))throw new R(ye,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(t){let n=L(t);return this.resolveAndAssert(n)}async resolveOperationWireContext(t){let n=Qe(t);return this.resolveAndAssert(n)}activateSession(t){this.session.activeComposition=t,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(C(t))}async normalizeOperationRequest(t,n){let s={...t,composition:Se(n)};if(s.target.kind==="route"){let o=s.target,i=L(o),r=await this.resolveAndAssert(i);s.target={...o,composition:Se(r)}}return s}handleEngineEvent(t){let n=t?.composition;if(!Ge(n))return;let s=C(n);this.session.subscribedCompositionKeys.has(s)&&(this.eventDispatcher.push(t),t.type==="site-edit.graph.invalidated"&&s===Ye(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(s)))}sendResult(t,n,s){this.sendJson({type:"response",payload:{id:t,kind:n,result:s}})}async handleRawMessage(t){try{await this.handleMessage(m.parseMessage(t,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let s=Xe(t);this.sendJson({type:"error",payload:{...s,message:G(n),code:J(n)}})}}sendJson(t){this.options.socket.send(m.stringifyMessage(t))}async handleBufferedOperation(t,n,s){this.eventDispatcher.beginBuffering();try{let o=await s();this.sendResult(t,n,o)}catch(o){this.sendJson({type:"error",payload:{id:t,kind:n,message:G(o),code:J(o)}})}finally{this.eventDispatcher.flush()}}},R=class extends Error{constructor(t,n){super(n),this.code=t,this.name="BridgeProtocolError"}},Q=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))}},Y=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()},s=this.queue.then(n,n);return this.queue=s.then(()=>{},()=>{}),s}};function Le(e,t){if(!f(e)||typeof e.id!="string"||!m.isRequestKind(e.kind))return!1;let n=k(e.composition);switch(e.kind){case"initialize-session":return n&&(e.mutationContext===void 0||z(e.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof e.key=="string"&&n&&ze(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":return n&&Je(e.request)&&(e.request.mutationContext===void 0||z(e.request.mutationContext))&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return n&&e.mutationContext===void 0||n&&z(e.mutationContext)}}function ze(e){if(e===void 0)return!0;if(!f(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(s=>s==="key"||s==="instanceIndex"||s==="instanceCount")}function Je(e){return f(e)&&typeof e.id=="string"&&typeof e.siteId=="string"&&typeof e.baseSnapshotId=="string"&&k(e.composition)&&typeof e.kind=="string"&&f(e.target)&&(e.target.kind!=="route"||k(e.target.composition))&&typeof e.documentVersion=="number"&&Number.isInteger(e.documentVersion)&&e.documentVersion>=0&&f(e.params)}function k(e){return f(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 Ge(e){return f(e)&&typeof e.routeId=="string"&&(e.effectiveTemplateId===void 0||typeof e.effectiveTemplateId=="string")}function L(e){if(k(e.composition))return e.composition;throw new R(Z,"A composition selector is required for this request.")}function Qe(e){if(f(e)&&k(e.composition))return e.composition;throw new R(Z,"A composition selector is required for execute.")}function Se(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function Ye(e){return e.activeComposition?C(e.activeComposition):null}function C(e){return JSON.stringify([e.routeId,e.effectiveTemplateId??null])}function x(e,t){return f(e)&&e.ok===!0?{...e,composition:t}:e}function Ze(e,t){return!f(e)||e.ok!==!0?e:{...e,...t?{composition:t}:{},data:Ie(e.data)}}function Ie(e){let t=e;return ge(t.routeId?t:{...t,routeId:t.composition.routeId})}function f(e){return typeof e=="object"&&e!==null}function z(e){return f(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 J(e){if(!(!f(e)||typeof e.code!="string"||e.code.length===0))return e.code}function G(e){return e instanceof Error||f(e)&&typeof e.message=="string"&&e.message.length>0?e.message:"Unknown websocket operation failure"}function Xe(e){try{return m.extractRequestInfo(JSON.parse(e))}catch{return{id:"unknown",kind:"get-document"}}}var F=class{constructor(){this.tail=Promise.resolve()}run(t){let n=this.tail.then(t,t);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as ht,computeSiteEditCompositionKey as b}from"@vcp-dev/contracts";var pt="127.0.0.1",ft=30218,lt="/__vcp/site-edit/bridge",Ee=6*1024*1024,mt=Ee*2,gt=3e3,we=50,te=".vcp/current-snapshot.json",xe="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function _e(e,t,n={}){let s=new ne(yt(t),e,!1,n);return s.start({listen:!1}),s}var ne=class{constructor(t,n=et.createServer((i,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),s=!0,o={}){this.options=t,this.server=n,this.closeHttpServerOnStop=s,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new F,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=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()}start(t={}){if(this.isStarted)return;this.stopping=!1;try{P.mkdirSync(g.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(s){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${s instanceof Error?s.message:String(s)}`)}this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime();let n=bt({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=n.stop,this.stopWatching=new dt({projectRoot:this.options.projectRoot,watch_dirs:St(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??we,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:s=>this.handleFileChanged(s),on_batch_change:s=>this.handleFilesChanged(s)}).start(),t.listen!==!1&&this.server.listen(this.options.port,this.options.host)}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=Ct(),n=Rt(this.options);t.get(n)===this&&t.delete(n),this.isStarted=!1}createRuntime(t,n=null){let s=this.runtime;this.activeHistoryStore=t??null;let o=(this.dependencies.createRuntime??ot)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:i=>{"version"in i&&typeof i.version=="number"&&this.recordRuntimeVersion(i.version,"composition"in i?i.composition:void 0);for(let r of this.eventListeners)r(i)},...t?{historyStore:t}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=n,s?.dispose()}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 s=wt(t.composition),o=this.getRuntime(),i=y(s);await o.initializeComposition(i);let a=o.getDocument(i).data;await this.recordRuntimeVersion(a.version,s);let u=(await o.getHistoryStatus()).data;return{ok:!0,composition:s,data:{document:a,historyStatus:u}}}),getDocument:async t=>{let n=this.getRuntime(),s=y(t);await n.initializeComposition(s);let o=n.getDocument(s);return await this.recordRuntimeVersion(o.data.version,t),{...o,composition:t}},getHistoryStatus:async t=>({...await this.getRuntime().getHistoryStatus(),composition:t}),getStyleContext:async t=>this.getRuntime().getStyleContext(y(t)),getObjectEditContext:async(t,n,s)=>this.getRuntime().getObjectEditContext(t,y(n),s),getClassNameSource:async(t,n)=>this.getRuntime().getClassNameSource(t,y(n)),getSourceTree:async t=>this.getRuntime().getSourceTree(y(t)),getSourceFile:async t=>this.getRuntime().getSourceFile({path:t.path,composition:y(t.composition)}),resolveSourceLocation:async t=>this.getRuntime().resolveSourceLocation({key:t.key,composition:y(t.composition)}),execute:(t,n)=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return X(s,t);let o=await this.getRuntime().executeOperation(t);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o}),undo:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return X(n,void 0,t);let s=await this.getRuntime().undo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition??t),s.composition?s:{...s,composition:t}}),redo:t=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return X(n,void 0,t);let s=await this.getRuntime().redo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition??t),s.composition?s:{...s,composition:t}}),subscribe:t=>(this.eventListeners.add(t),()=>this.eventListeners.delete(t))}}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 s=await(this.dependencies.openHistoryStore??it)(xt({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:t}));if(!this.isStarted)return;let o=s.readDocumentVersion?await s.readDocumentVersion():0,i=this.compositions[0]?.context;if(i){let r=b(i);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(s,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 s=n?b(n):null;s&&this.runtimeVersionFloors.set(s,Math.max(this.runtimeVersionFloors.get(s)??0,t));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let i=s?this.runtimeVersionFloors.get(s)??t:t;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(i)).catch(()=>{}),this.versionPersistence}handleFileChanged(t){this.handleFilesChanged([t])}handleFilesChanged(t){for(let s of t)this.pendingChangedFiles.add(s);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??we;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,s)=>n.localeCompare(s));if(this.pendingChangedFiles.clear(),t.some(At)&&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=P.readFileSync(g.join(this.options.projectRoot,te),"utf8"),n=JSON.parse(t),s=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!s||se(s)!==s?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):s}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 o=await this.bindHistorySession(t);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 s=new Date().toISOString();for(let o of this.compositions){let i={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:t,previousSnapshotId:n,at:s};for(let r of this.eventListeners)try{r(i)}catch(a){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${a instanceof Error?a.message:String(a)}`)}}try{this.options.onSnapshotIdChanged?.(t,n)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(t=!1){try{let n=It(this.options.projectRoot),s=ut(n),o=s.length>0?s:(this.options.runtime.routes??[]).map(u=>({...u})),i=at({files:n});if(i.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=new Map(this.compositions.map(u=>[b(u.context),u])),a=new Map(i.map(u=>[b(u.context),u]));if(this.runtime){for(let[u,c]of r){let h=a.get(u);(!h||h.entryFile!==c.entryFile)&&this.runtime.unregisterComposition(y(c.context))}for(let[u,c]of a){let h=r.get(u);(!h||h.entryFile!==c.entryFile)&&this.runtime.registerComposition(c.context,c.entryFile)}}return this.routes=o,this.compositions=i,this.compositionFiles=n,this.compositionResolutionError=null,!0}catch(n){let s=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=s,t)throw s;return!1}}resolveComposition(t){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=ct(t.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let s=this.routes.find(r=>r.routeId===n);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let o=rt({selector:{routeId:n,...t.templateId?{templateId:t.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:s.entryFile}),i=b(o.context);if(!this.compositions.some(r=>b(r.context)===i))throw new Error(`Composition is not registered by the preview bridge: ${i}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(t=>{let n=this.runtimeVersionFloors.get(b(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 yt(e){return{tenantId:e.tenantId,siteId:e.siteId,snapshotId:e.snapshotId,projectRoot:g.resolve(e.projectRoot??process.cwd()),basePath:e.basePath??"",host:e.host??pt,port:e.port??ft,path:e.path??lt,sessionKey:e.sessionKey??Ht(),historyRoot:e.historyRoot,allowedOrigins:e.allowedOrigins??[],runtime:e.runtime??{},onSnapshotIdChanged:e.onSnapshotIdChanged}}function St(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 It(e){let t=[],n=["app","src/app",".vcp"],s=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=i=>{if(!P.existsSync(i))return;let r;try{r=P.readdirSync(i,{withFileTypes:!0})}catch{return}for(let a of r){if(a.name.startsWith(".")&&a.name!==".vcp")continue;if(a.isDirectory()){if(s.has(a.name))continue;o(g.join(i,a.name));continue}if(!a.isFile())continue;let u=g.join(i,a.name),c="";try{c=P.readFileSync(u,"utf8")}catch{}t.push({path:g.relative(e,u).replaceAll(g.sep,"/"),content:c})}};for(let i of n)o(g.join(e,i));return t.sort((i,r)=>i.path.localeCompare(r.path))}function wt(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 y(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function xt(e){return g.join(g.resolve(e.root),se(e.siteId),se(e.snapshotId),be("sha256").update(e.editingSessionId).digest("hex").slice(0,32))}function se(e){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e)&&e!=="."&&e!==".."?e:be("sha256").update(e).digest("hex").slice(0,32)}function Ct(){let e=globalThis;return e[xe]??=new Map,e[xe]}function Rt(e){return`${e.projectRoot}:${e.host}:${e.port}:${e.path}:${e.historyRoot??""}`}function X(e,t,n){let s=n??(t?.composition?{routeId:t.composition.routeId,...t.composition.templateId?{effectiveTemplateId:t.composition.templateId}:{}}:void 0);if(!s)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:s,error:{code:e.code,message:e.message},data:{requestId:t?.id??"mutation-context",kind:t?.kind??"update-text",composition:s,target:t?.target??{kind:"node",key:""},resultVersion:t?.documentVersion??0}}}function bt(e){let t=new st({maxPayload:Ee,noServer:!0}),n=new WeakSet,s=i=>{new j({socket:new oe(i),engine:e.engine,validateOperationRequest:r=>ht.safeParse(r).success})},o=(i,r,a)=>{if(!vt(i,e))return;let u=Et(i,e);if(!u){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),r.destroy();return}u.authenticatedByHeader&&n.add(i),e.handleUpgrade(i,r,a,c=>{e.emit("connection",c,i)})};return t.server.on("upgrade",o),e.on("connection",(i,r)=>{if(n.has(r)){s(i);return}be(i,t.sessionKey,()=>{s(i)})}),e.stop=()=>{t.server.off("upgrade",o);for(let i of e.clients)i.close(1001,"bridge stopped");e.close()},e}var ot=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===Et.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>le){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,n){this.socket.close(e,n)}};function Re(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function Ee(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let s=tt(t.headers["x-vcp-site-edit-key"]),o=Tt(s,e.sessionKey),i=tt(t.headers.host),r=tt(t.headers.origin);return i&&!_e(i,e.host,r)?null:r?ke(r,i,e.allowedOrigins)?{authenticatedByHeader:o}:null:o?{authenticatedByHeader:!0}:null}function be(t,e,n){let s=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},me),o,i=()=>{clearTimeout(s),t.off("message",o),t.off("close",i)},r=()=>{i(),t.close(1008,"bridge authentication failed")};o=a=>{try{let u=m.parseMessage(a.toString());if(u.type!=="auth"||!Tt(u.payload.key,e)){r();return}i(),t.readyState===Et.OPEN&&t.send(m.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{r()}},t.on("message",o),t.on("close",i)}function Tt(t,e){if(!t)return!1;let n=Buffer.from(t),s=Buffer.from(e);return n.length===s.length&&ee(n,s)}function _e(t,e,n){let s=vt(t),o=vt(e);return s===o||Ct(o)&&Ct(s)&&(!n||it(n,t))||Te(o)&&(!n||it(n,t))}function Te(t){return t==="0.0.0.0"||t==="::"}function Ct(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function ke(t,e,n){return n.includes(t)||(e?it(t,e):!1)}function it(t,e){try{return new URL(t).host===kt(e)}catch{return!1}}function vt(t){let e=kt(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function kt(t){return t.trim().toLowerCase()}function tt(t){return Array.isArray(t)?t[0]:t}function Pe(){return te(32).toString("base64url")}function He(t){let e=t.replaceAll("\\","/");return e===et||e.endsWith(`/${et}`)}var Be="next",Mt="127.0.0.1",De=3e3,V="/__vcp/site-edit/bridge",Nt="/__vcp/site-edit/health",Oe="probe",Me="render",Ht="/_error",q="/__vcp/site-edit/client/",Ne=`${q}auto-bootstrap.js`,jt='data-vcp-site-edit-bootstrap="true"',je=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],Fe="@vcp-dev/site-edit-engine/webpack-loader",Ue=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,Ke=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],Ve=new Set(["auto-bootstrap.js","client.js","composition-key.js","designer-trace.js","document-codec.js","protocol.js","window-message.js"]);async function Xn(t={},e={}){let n=e.process??process,s=e.createServer??ut.createServer,o=e.attachBridgeServer??_t,i=e.loadNext??In,r=H.resolve(t.projectRoot??process.cwd()),a=t.host??Mt,u=t.port??vn(n.env.PORT)??De,c=t.bridgePath??V,h=Ge(c),d=ln(),p=t.basePath??n.env.VCP_SITE_PREVIEW_BASE_PATH??"/",l=(t.tenantId??n.env.VCP_TENANT_ID)?.trim()||void 0,S=await i(r),I=qe(r),w=S({dev:!0,dir:r,hostname:a,port:u,...$e(I)}),Kt=w.getRequestHandler(),Vt=t.siteId??n.env.VCP_SITE_ID??"018f6f4d-a9b8-7120-8000-000000000120",ht=t.snapshotId??n.env.VCP_SNAPSHOT_ID??Cn(r),pt=t.historyRoot??n.env.VCP_SITE_EDIT_HISTORY_ROOT,ft=pt?.trim()?H.resolve(pt):void 0,D=null,lt={launchId:Ae(),pid:process.pid,snapshotId:ht},E=s((T,N)=>{nn({basePath:p,bridgePath:c,bridgeHealthPath:h,bridgeSessionKey:d,getBridgeHealth:()=>D?.getHealth()??cn(c),launch:lt,runRenderProbe:()=>on(w),handle:Kt,request:T,response:N}).catch(qt=>{console.error(qt),N.headersSent||(N.statusCode=500),N.end("site edit preview request failed")})}),$=null,O=null,W=()=>O||(K(()=>n.off("SIGINT",M)),K(()=>n.off("SIGTERM",M)),K(()=>$?.()),$=null,K(()=>D?.stop()),D=null,O=Promise.allSettled([En(E),bn(()=>w.close())]).then(()=>{}),O),M=()=>{W()};try{return await w.prepare(),D=o(E,{...l?{tenantId:l}:{},siteId:Vt,snapshotId:ht,projectRoot:r,basePath:p,host:a,port:u,path:c,sessionKey:d,...ft?{historyRoot:ft}:{},onSnapshotIdChanged:T=>{lt.snapshotId=T}}),$=ze({server:E,bridgePath:c,handleUpgrade:w.getUpgradeHandler()}),n.on("SIGINT",M),n.on("SIGTERM",M),await Rn(E,u,a),{bundler:I,close:W,host:a,port:_n(E,u),server:E}}catch(T){throw await W(),T}}function qe(t){let e=We(t);return e&&Le(e)?"webpack":"turbopack"}function $e(t){return t==="webpack"?{webpack:!0}:{turbopack:!0}}function We(t){for(let e of je){let n=B(t,e);if(_(n))return b(n,"utf8")}return null}function Le(t){return t.includes(Fe)?!0:Ue.test(t)&&Ke.some(e=>e.test(t))}function ze(t){let e=(n,s,o)=>{Je(n.url,t.bridgePath)||t.handleUpgrade(n,s,o).catch(i=>{console.error(i),s.destroyed||s.destroy()})};return t.server.on("upgrade",e),()=>t.server.off("upgrade",e)}function Je(t,e){return new URL(t??"/","http://localhost").pathname===e}function Ge(t=V){if(t===V)return Nt;let e=(t.startsWith("/")?t:`/${t}`).replace(/\/+$/,"");return e.endsWith("/bridge")?`${e.slice(0,-7)}/health`:e?`${e}/health`:"/health"}function Qe(t){let e=JSON.stringify({basePath:t.basePath,wsPath:t.bridgePath,bridge_session_key:t.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:t.runtimeVersions??Ye()});return`<script type="module" ${jt}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(Ne)};void startSiteEditAutoBootstrap(window, ${e}).catch(() => undefined);</script>`}function Ye(){return{bridge:rt("@vcp-dev/site-edit-bridge"),contracts:rt("@vcp-dev/contracts"),engine:rt("@vcp-dev/site-edit-engine")}}function rt(t){let e=Bt(import.meta.url);try{let n=e.resolve(t),s=Ze(n,t);if(!s)return;let o=JSON.parse(b(s,"utf8"));return typeof o.version=="string"?o.version:void 0}catch{return}}function Ze(t,e){let n=H.parse(t).root,s=A(t);for(;s&&s!==n;){let o=B(s,"package.json");if(_(o))try{if(JSON.parse(b(o,"utf8")).name===e)return o}catch{return}s=A(s)}}function Xe(t,e){if(t.includes(jt))return t;let n=Qe(e),s=/<\/body\s*>/i.exec(t);return s?`${t.slice(0,s.index)}${n}${t.slice(s.index)}`:`${t}${n}`}function ts(t,e=A(Ot(import.meta.url)),n=_){return Ft(t,e,n)?.path??null}function Ft(t,e=A(Ot(import.meta.url)),n=_){let s=new URL(t??"/","http://localhost").pathname;if(!s.startsWith(q))return null;let o=s.slice(q.length);if(!Ve.has(o))return null;let i=dt(wn(e),o);if(Dt(e)!=="src")return{path:i,requiresTranspile:!1};let r=dt(e,o.replace(/\.js$/,".ts"));return n(r)?{path:r,requiresTranspile:!0}:{path:i,requiresTranspile:!1}}async function tn(t,e){let n=Ft(t.url);if(!n)return!1;if(!_(n.path))return e.writeHead(404),e.end("site edit client asset not found"),!0;let s=n.requiresTranspile?await en(b(n.path,"utf8")):b(n.path);return e.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),e.end(s),!0}async function en(t){let e=await import("typescript");return e.transpileModule(t,{compilerOptions:{module:e.ModuleKind.ES2022,target:e.ScriptTarget.ES2022}}).outputText}async function nn({basePath:t,bridgePath:e,bridgeHealthPath:n,bridgeSessionKey:s,getBridgeHealth:o,launch:i,runRenderProbe:r,handle:a,request:u,response:c}){if(await an(u,c,n,o,i,r)||await tn(u,c))return;let h=un(u),d=dn(u),p=h||d?fn(c,{basePath:t,bridgePath:e,bridgeSessionKey:s,patchNextDevChunk:d}):()=>{};try{await a(u,c)}catch(l){throw p(),l}}function sn(t){let e=t instanceof Error?t.message:String(t),n=t&&typeof t=="object"&&"code"in t?t.code:void 0;return/\.next[/\\]/.test(e)||/chunks[/\\]ssr[/\\]/.test(e)?n==="ENOENT"||n==="MODULE_NOT_FOUND"?{verdict:"artifact_broken",detail:e}:e.includes("Cannot find module")?{verdict:"artifact_broken",detail:e}:{verdict:"inconclusive",detail:e}:{verdict:"inconclusive",detail:e}}async function on(t){let e=new ut.IncomingMessage(new Pt);e.method="GET",e.url=Ht,e.headers={host:Mt};let n=new ut.ServerResponse(e);n.assignSocket(new Pt);try{return await t.renderToHTML(e,n,Ht,{}),{verdict:"ok"}}catch(s){return sn(s)}finally{try{let s=n.socket;s&&(n.detachSocket(s),s.destroy())}catch{}}}function rn(t){return new URL(t??"/","http://localhost").searchParams.get(Oe)===Me}async function an(t,e,n,s,o,i){if(t.method!=="GET"||new URL(t.url??"/","http://localhost").pathname!==n)return!1;let a=s(),u=rn(t.url)?await i():void 0,c={...a,launch:o,...u?{render:u}:{}};return e.writeHead(a.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(c)),!0}function cn(t){return{ok:!1,bridgePath:t,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function un(t){return hn(t)?(t.headers["accept-encoding"]="identity",!0):!1}function dn(t){return pn(t)?(t.headers["accept-encoding"]="identity",!0):!1}function hn(t){if(t.method!=="GET")return!1;let e=new URL(t.url??"/","http://localhost").pathname;if(e.startsWith("/_next/")||e.startsWith(q)||e===V||e===Nt)return!1;let n=t.headers.accept??"";return n===""||n.includes("text/html")}function pn(t){if(t.method!=="GET"||!String(t.headers["user-agent"]??"").includes("Firefox"))return!1;let n=new URL(t.url??"/","http://localhost").pathname;return n.startsWith("/_next/static/chunks/")&&n.endsWith(".js")}function fn(t,e){let n=[],s=t,o=s.flushHeaders.bind(t),i=s.write.bind(t),r=s.end.bind(t),a=!1;s.flushHeaders=()=>{};let u=()=>{if(e.patchNextDevChunk||a)return!1;let c=Buffer.concat(n);if(!/<body\b[^>]*>/iu.test(c.toString("utf8")))return!1;let h=At(t,c,e);return h!==c&&!t.headersSent&&t.removeHeader("content-length"),i(h),n.length=0,a=!0,!0};return s.write=(c,h,d)=>a?(c!==void 0&&i(at(c,ct(h))),U(h),U(d),!0):(c!==void 0&&(n.push(at(c,ct(h))),u()),U(h),U(d),!0),s.end=(c,h,d)=>{let p=typeof c=="function"?void 0:c;if(a)return s.write=i,s.end=r,s.flushHeaders=o,p===void 0?typeof c=="function"?r(c):typeof h=="function"?r(h):typeof d=="function"?r(d):r():typeof h=="function"?r(p,h):typeof d=="function"?r(p,h,d):r(p,h);p!==void 0&&n.push(at(p,ct(h))),s.write=i,s.end=r,s.flushHeaders=o;let l=Buffer.concat(n),S=At(t,l,e);S!==l&&!t.headersSent&&t.removeHeader("content-length");let I=typeof c=="function"?c:typeof h=="function"?h:typeof d=="function"?d:void 0;return I?r(S,I):r(S)},()=>{s.write=i,s.end=r,s.flushHeaders=o}}function At(t,e,n){if(mn(t,e))return Buffer.from(Xe(e.toString("utf8"),n));if(n.patchNextDevChunk&&gn(t)){let s=e.toString("utf8"),o=yn(s);if(o!==s)return Buffer.from(o)}return e}function ln(){let t=new Uint8Array(32);return globalThis.crypto.getRandomValues(t),Buffer.from(t).toString("base64url")}function mn(t,e){if(t.statusCode<200||t.statusCode>=300)return!1;let n=t.getHeader("content-type"),s=Array.isArray(n)?n.join(";"):String(n??"");return e.toString("utf8",0,256).includes("<html")&&(s===""||s.includes("text/html"))}function gn(t){if(t.statusCode<200||t.statusCode>=300)return!1;let e=t.getHeader("content-type"),n=Array.isArray(e)?e.join(";"):String(e??"");return n===""||n.includes("javascript")||n.includes("ecmascript")}function yn(t){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}u.authenticatedByHeader&&n.add(i),t.handleUpgrade(i,r,a,c=>{t.emit("connection",c,i)})};return e.server.on("upgrade",o),t.on("connection",(i,r)=>{if(n.has(r)){s(i);return}_t(i,e.sessionKey,()=>{s(i)})}),t.stop=()=>{e.server.off("upgrade",o);for(let i of t.clients)i.close(1001,"bridge stopped");t.close()},t}var oe=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===ve.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(t,"utf8")>mt){this.socket.close(1013,"bridge backpressure");return}this.socket.send(t)}}close(t,n){this.socket.close(t,n)}};function vt(e,t){return new URL(e.url??"/","http://localhost").pathname===t.path}function Et(e,t){if(new URL(e.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let s=ee(e.headers["x-vcp-site-edit-key"]),o=Te(s,t.sessionKey),i=ee(e.headers.host),r=ee(e.headers.origin);return i&&!Tt(i,t.host,r)?null:r?Pt(r,i,t.allowedOrigins)?{authenticatedByHeader:o}:null:o?{authenticatedByHeader:!0}:null}function _t(e,t,n){let s=setTimeout(()=>{e.close(1008,"bridge authentication timeout")},gt),o,i=()=>{clearTimeout(s),e.off("message",o),e.off("close",i)},r=()=>{i(),e.close(1008,"bridge authentication failed")};o=a=>{try{let u=m.parseMessage(a.toString());if(u.type!=="auth"||!Te(u.payload.key,t)){r();return}i(),e.readyState===ve.OPEN&&e.send(m.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{r()}},e.on("message",o),e.on("close",i)}function Te(e,t){if(!e)return!1;let n=Buffer.from(e),s=Buffer.from(t);return n.length===s.length&&nt(n,s)}function Tt(e,t,n){let s=Re(e),o=Re(t);return s===o||Ce(o)&&Ce(s)&&(!n||ie(n,e))||kt(o)&&(!n||ie(n,e))}function kt(e){return e==="0.0.0.0"||e==="::"}function Ce(e){return e==="localhost"||e==="127.0.0.1"||e==="::1"}function Pt(e,t,n){return n.includes(e)||(t?ie(e,t):!1)}function ie(e,t){try{return new URL(e).host===ke(t)}catch{return!1}}function Re(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 ee(e){return Array.isArray(e)?e[0]:e}function Ht(){return tt(32).toString("base64url")}function At(e){let t=e.replaceAll("\\","/");return t===te||t.endsWith(`/${te}`)}var Dt="next",Me="127.0.0.1",Ot=3e3,V="/__vcp/site-edit/bridge",Ne="/__vcp/site-edit/health",Mt="probe",Nt="render",He="/_error",q="/__vcp/site-edit/client/",jt=`${q}auto-bootstrap.js`,je='data-vcp-site-edit-bootstrap="true"',Ft=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],Ut="@vcp-dev/site-edit-engine/webpack-loader",Kt=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,Vt=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],qt=new Set(["auto-bootstrap.js","client.js","composition-key.js","designer-trace.js","document-codec.js","protocol.js","window-message.js"]);async function es(e={},t={}){let n=t.process??process,s=t.createServer??ue.createServer,o=t.attachBridgeServer??_e,i=t.loadNext??wn,r=H.resolve(e.projectRoot??process.cwd()),a=e.host??Me,u=e.port??bn(n.env.PORT)??Ot,c=e.bridgePath??V,h=Qt(c),d=mn(),p=e.basePath??n.env.VCP_SITE_PREVIEW_BASE_PATH??"/",l=(e.tenantId??n.env.VCP_TENANT_ID)?.trim()||void 0,S=await i(r),I=$t(r),w=S({dev:!0,dir:r,hostname:a,port:u,...Wt(I)}),Ke=w.getRequestHandler(),Ve=e.siteId??n.env.VCP_SITE_ID??"018f6f4d-a9b8-7120-8000-000000000120",he=e.snapshotId??n.env.VCP_SNAPSHOT_ID??Rn(r),pe=e.historyRoot??n.env.VCP_SITE_EDIT_HISTORY_ROOT,fe=pe?.trim()?H.resolve(pe):void 0,D=null,le={launchId:Bt(),pid:process.pid,snapshotId:he},v=s((T,N)=>{sn({basePath:p,bridgePath:c,bridgeHealthPath:h,bridgeSessionKey:d,getBridgeHealth:()=>D?.getHealth()??un(c),launch:le,runRenderProbe:()=>rn(w),handle:Ke,request:T,response:N}).catch(qe=>{console.error(qe),N.headersSent||(N.statusCode=500),N.end("site edit preview request failed")})}),$=null,O=null,W=()=>O||(K(()=>n.off("SIGINT",M)),K(()=>n.off("SIGTERM",M)),K(()=>$?.()),$=null,K(()=>D?.stop()),D=null,O=Promise.allSettled([En(v),_n(()=>w.close())]).then(()=>{}),O),M=()=>{W()};try{return await w.prepare(),D=o(v,{...l?{tenantId:l}:{},siteId:Ve,snapshotId:he,projectRoot:r,basePath:p,host:a,port:u,path:c,sessionKey:d,...fe?{historyRoot:fe}:{},onSnapshotIdChanged:T=>{le.snapshotId=T}}),$=Jt({server:v,bridgePath:c,handleUpgrade:w.getUpgradeHandler()}),n.on("SIGINT",M),n.on("SIGTERM",M),await vn(v,u,a),{bundler:I,close:W,host:a,port:Tn(v,u),server:v}}catch(T){throw await W(),T}}function $t(e){let t=Lt(e);return t&&zt(t)?"webpack":"turbopack"}function Wt(e){return e==="webpack"?{webpack:!0}:{turbopack:!0}}function Lt(e){for(let t of Ft){let n=B(e,t);if(_(n))return E(n,"utf8")}return null}function zt(e){return e.includes(Ut)?!0:Kt.test(e)&&Vt.some(t=>t.test(e))}function Jt(e){let t=(n,s,o)=>{Gt(n.url,e.bridgePath)||e.handleUpgrade(n,s,o).catch(i=>{console.error(i),s.destroyed||s.destroy()})};return e.server.on("upgrade",t),()=>e.server.off("upgrade",t)}function Gt(e,t){return new URL(e??"/","http://localhost").pathname===t}function Qt(e=V){if(e===V)return Ne;let t=(e.startsWith("/")?e:`/${e}`).replace(/\/+$/,"");return t.endsWith("/bridge")?`${t.slice(0,-7)}/health`:t?`${t}/health`:"/health"}function Yt(e){let t=JSON.stringify({basePath:e.basePath,wsPath:e.bridgePath,bridge_session_key:e.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:e.runtimeVersions??Zt()});return`<script type="module" ${je}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(jt)};void startSiteEditAutoBootstrap(window, ${t}).catch(() => undefined);</script>`}function Zt(){return{bridge:re("@vcp-dev/site-edit-bridge"),contracts:re("@vcp-dev/contracts"),engine:re("@vcp-dev/site-edit-engine")}}function re(e){let t=Be(import.meta.url);try{let n=t.resolve(e),s=Xt(n,e);if(!s)return;let o=JSON.parse(E(s,"utf8"));return typeof o.version=="string"?o.version:void 0}catch{return}}function Xt(e,t){let n=H.parse(e).root,s=A(e);for(;s&&s!==n;){let o=B(s,"package.json");if(_(o))try{if(JSON.parse(E(o,"utf8")).name===t)return o}catch{return}s=A(s)}}function en(e,t){if(e.includes(je))return e;let n=Yt(t),s=/<\/body\s*>/i.exec(e);return s?`${e.slice(0,s.index)}${n}${e.slice(s.index)}`:`${e}${n}`}function ts(e,t=A(Oe(import.meta.url)),n=_){return Fe(e,t,n)?.path??null}function Fe(e,t=A(Oe(import.meta.url)),n=_){let s=new URL(e??"/","http://localhost").pathname;if(!s.startsWith(q))return null;let o=s.slice(q.length);if(!qt.has(o))return null;let i=de(xn(t),o);if(De(t)!=="src")return{path:i,requiresTranspile:!1};let r=de(t,o.replace(/\.js$/,".ts"));return n(r)?{path:r,requiresTranspile:!0}:{path:i,requiresTranspile:!1}}async function tn(e,t){let n=Fe(e.url);if(!n)return!1;if(!_(n.path))return t.writeHead(404),t.end("site edit client asset not found"),!0;let s=n.requiresTranspile?await nn(E(n.path,"utf8")):E(n.path);return t.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),t.end(s),!0}async function nn(e){let t=await import("typescript");return t.transpileModule(e,{compilerOptions:{module:t.ModuleKind.ES2022,target:t.ScriptTarget.ES2022}}).outputText}async function sn({basePath:e,bridgePath:t,bridgeHealthPath:n,bridgeSessionKey:s,getBridgeHealth:o,launch:i,runRenderProbe:r,handle:a,request:u,response:c}){if(await cn(u,c,n,o,i,r)||await tn(u,c))return;let h=dn(u),d=hn(u),p=h||d?ln(c,{basePath:e,bridgePath:t,bridgeSessionKey:s,patchNextDevChunk:d}):()=>{};try{await a(u,c)}catch(l){throw p(),l}}function on(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 rn(e){let t=new ue.IncomingMessage(new Pe);t.method="GET",t.url=He,t.headers={host:Me};let n=new ue.ServerResponse(t);n.assignSocket(new Pe);try{return await e.renderToHTML(t,n,He,{}),{verdict:"ok"}}catch(s){return on(s)}finally{try{let s=n.socket;s&&(n.detachSocket(s),s.destroy())}catch{}}}function an(e){return new URL(e??"/","http://localhost").searchParams.get(Mt)===Nt}async function cn(e,t,n,s,o,i){if(e.method!=="GET"||new URL(e.url??"/","http://localhost").pathname!==n)return!1;let a=s(),u=an(e.url)?await i():void 0,c={...a,launch:o,...u?{render:u}:{}};return t.writeHead(a.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify(c)),!0}function un(e){return{ok:!1,bridgePath:e,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function dn(e){return pn(e)?(e.headers["accept-encoding"]="identity",!0):!1}function hn(e){return fn(e)?(e.headers["accept-encoding"]="identity",!0):!1}function pn(e){if(e.method!=="GET")return!1;let t=new URL(e.url??"/","http://localhost").pathname;if(t.startsWith("/_next/")||t.startsWith(q)||t===V||t===Ne)return!1;let n=e.headers.accept??"";return n===""||n.includes("text/html")}function fn(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 ln(e,t){let n=[],s=e,o=s.flushHeaders.bind(e),i=s.write.bind(e),r=s.end.bind(e),a=!1;s.flushHeaders=()=>{};let u=()=>{if(t.patchNextDevChunk||a)return!1;let c=Buffer.concat(n);if(!/<body\b[^>]*>/iu.test(c.toString("utf8")))return!1;let h=Ae(e,c,t);return h!==c&&!e.headersSent&&e.removeHeader("content-length"),i(h),n.length=0,a=!0,!0};return s.write=(c,h,d)=>a?(c!==void 0&&i(ae(c,ce(h))),U(h),U(d),!0):(c!==void 0&&(n.push(ae(c,ce(h))),u()),U(h),U(d),!0),s.end=(c,h,d)=>{let p=typeof c=="function"?void 0:c;if(a)return s.write=i,s.end=r,s.flushHeaders=o,p===void 0?typeof c=="function"?r(c):typeof h=="function"?r(h):typeof d=="function"?r(d):r():typeof h=="function"?r(p,h):typeof d=="function"?r(p,h,d):r(p,h);p!==void 0&&n.push(ae(p,ce(h))),s.write=i,s.end=r,s.flushHeaders=o;let l=Buffer.concat(n),S=Ae(e,l,t);S!==l&&!e.headersSent&&e.removeHeader("content-length");let I=typeof c=="function"?c:typeof h=="function"?h:typeof d=="function"?d:void 0;return I?r(S,I):r(S)},()=>{s.write=i,s.end=r,s.flushHeaders=o}}function Ae(e,t,n){if(gn(e,t))return Buffer.from(en(t.toString("utf8"),n));if(n.patchNextDevChunk&&yn(e)){let s=t.toString("utf8"),o=Sn(s);if(o!==s)return Buffer.from(o)}return t}function mn(){let e=new Uint8Array(32);return globalThis.crypto.getRandomValues(e),Buffer.from(e).toString("base64url")}function gn(e,t){if(e.statusCode<200||e.statusCode>=300)return!1;let n=e.getHeader("content-type"),s=Array.isArray(n)?n.join(";"):String(n??"");return t.toString("utf8",0,256).includes("<html")&&(s===""||s.includes("text/html"))}function yn(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 Sn(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(`
|
|
5
5
|
`),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join(`
|
|
6
|
-
`)],[["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,[s,o])=>
|
|
6
|
+
`)],[["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,[s,o])=>In(n,s,o),e)}function In(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 ae(e,t){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e):Buffer.from(String(e),t)}function ce(e){return typeof e=="string"?e:void 0}function U(e){typeof e=="function"&&e()}async function wn(e){let n=Be(B(e,"package.json"))(Dt);return n.default??n}function xn(e){return De(e)!=="src"?e:de(Cn(e),"dist")}function Cn(e){return Ue(e)?H.posix.dirname(e):A(e)}function de(e,t){return Ue(e)?H.posix.join(e,t):B(e,t)}function Ue(e){return e.includes("/")&&!e.includes("\\")}function Rn(e){let t=B(e,".vcp/current-snapshot.json");if(!_(t))return"sandbox_worktree";try{let n=JSON.parse(E(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 bn(e){if(!e)return null;let t=Number.parseInt(e,10);return Number.isInteger(t)&&t>0&&t<=65535?t:null}function vn(e,t,n){return new Promise((s,o)=>{e.once("error",o),e.listen(t,n,()=>{e.off("error",o),s()})})}function En(e){return new Promise(t=>{e.close(()=>t())})}function K(e){try{e()}catch{}}function _n(e){try{return e()}catch(t){return Promise.reject(t)}}function Tn(e,t){let n=e.address();return typeof n=="object"&&n?n.port:t}export{Jt as attachNextDevUpgradeHandler,on as classifySiteEditRenderProbeError,Yt as createSiteEditAutoBootstrapHtmlSnippet,en as injectSiteEditAutoBootstrap,an as isSiteEditRenderProbeRequested,Sn as patchNextFirefoxDevChunk,hn as prepareFirefoxNextDevChunkPatchRequest,dn as prepareSiteEditHtmlInjectionRequest,Qt as resolveSiteEditBridgeHealthPath,Fe as resolveSiteEditClientAsset,ts as resolveSiteEditClientAssetPath,$t as resolveSiteEditNextDevBundler,rn as runSiteEditRenderProbe,es as startSiteEditNextDevServer,nn as transpileSiteEditClientSource};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type SiteEditPropertyChange } from "@vcp-dev/contracts";
|
|
2
|
+
import type { SiteEditFrameRuntimeNodePatch } from "./window-message.js";
|
|
3
|
+
export type SiteEditPropertyPreviewElement = {
|
|
4
|
+
kind: "host" | "component";
|
|
5
|
+
tagName: string;
|
|
6
|
+
};
|
|
7
|
+
export type SiteEditPropertyPreviewInput = {
|
|
8
|
+
element: SiteEditPropertyPreviewElement;
|
|
9
|
+
runtimeAttributes: Readonly<Record<string, string>>;
|
|
10
|
+
scope: "selection" | "template";
|
|
11
|
+
changes: SiteEditPropertyChange[];
|
|
12
|
+
};
|
|
13
|
+
export type SiteEditPropertyPreviewResult = {
|
|
14
|
+
mode: "dom";
|
|
15
|
+
patch: SiteEditFrameRuntimeNodePatch;
|
|
16
|
+
} | {
|
|
17
|
+
mode: "after-render";
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function planPropertyPreview(input: SiteEditPropertyPreviewInput): SiteEditPropertyPreviewResult;
|
|
21
|
+
/**
|
|
22
|
+
* Returns a DOM name only when this source property has a known host mapping.
|
|
23
|
+
* Component props and generic names deliberately return null so consumers do
|
|
24
|
+
* not infer a source-to-DOM relationship from a coincidentally named attr.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getPropertyPreviewDomAttributeName(name: string): string | null;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SiteEditClassNameSourceStyleContext, SiteEditCommandSuccess, SiteEditCompositionContext, SiteEditCompositionSelector, SiteEditEvent, SiteEditGetClassNameSourceCommandResult, SiteEditMutationContext, SiteEditObjectEditContext, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditResolveSourceLocationInput, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
|
|
1
|
+
import type { SiteEditClassNameSourceStyleContext, SiteEditCommandSuccess, SiteEditCompositionContext, SiteEditCompositionSelector, SiteEditEvent, SiteEditGetClassNameSourceCommandResult, SiteEditMutationContext, SiteEditObjectEditContext, SiteEditOperationSelectionRef, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditResolveSourceLocationInput, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
|
|
2
2
|
type Awaitable<T> = T | Promise<T>;
|
|
3
3
|
/** The Bridge keeps this old shape at its boundary for compatibility with the
|
|
4
4
|
* durable history adapter. The selected composition is carried beside it. */
|
|
@@ -43,7 +43,12 @@ export type SiteEditBridgeRequest = {
|
|
|
43
43
|
kind: "get-style-context";
|
|
44
44
|
} & CompositionRequestFields) | ({
|
|
45
45
|
id: string;
|
|
46
|
-
kind: "get-object-edit-context"
|
|
46
|
+
kind: "get-object-edit-context";
|
|
47
|
+
key: string;
|
|
48
|
+
selectionRef?: SiteEditOperationSelectionRef;
|
|
49
|
+
} & CompositionRequestFields) | ({
|
|
50
|
+
id: string;
|
|
51
|
+
kind: "get-class-name-source" | "resolve-source-location";
|
|
47
52
|
key: string;
|
|
48
53
|
} & CompositionRequestFields) | ({
|
|
49
54
|
id: string;
|
|
@@ -112,7 +117,7 @@ export type SiteEditBridgeEngine = {
|
|
|
112
117
|
getDocument(composition: SiteEditCompositionContext): Awaitable<SiteEditCommandSuccess<SiteEditRenderDocument>>;
|
|
113
118
|
getHistoryStatus(composition: SiteEditCompositionContext): Awaitable<SiteEditCommandSuccess<SiteEditHistoryStatus>>;
|
|
114
119
|
getStyleContext(composition: SiteEditCompositionContext): Awaitable<SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null>>;
|
|
115
|
-
getObjectEditContext(key: string, composition: SiteEditCompositionContext): Awaitable<SiteEditCommandSuccess<SiteEditObjectEditContext>>;
|
|
120
|
+
getObjectEditContext(key: string, composition: SiteEditCompositionContext, selectionRef?: SiteEditOperationSelectionRef): Awaitable<SiteEditCommandSuccess<SiteEditObjectEditContext>>;
|
|
116
121
|
getClassNameSource(key: string, composition: SiteEditCompositionContext): Awaitable<SiteEditGetClassNameSourceCommandResult>;
|
|
117
122
|
getSourceTree(composition: SiteEditCompositionContext): Awaitable<SiteEditCommandSuccess<SiteEditSourceTreeResponse>>;
|
|
118
123
|
getSourceFile(input: {
|
package/dist/protocol.js
CHANGED
|
@@ -146,7 +146,7 @@ export class SiteEditBridgeSocketServer {
|
|
|
146
146
|
}
|
|
147
147
|
case "get-object-edit-context": {
|
|
148
148
|
const context = await this.resolveWireContext(payload);
|
|
149
|
-
this.sendResult(id, payload.kind, addComposition(await this.options.engine.getObjectEditContext(payload.key, context), context));
|
|
149
|
+
this.sendResult(id, payload.kind, addComposition(await this.options.engine.getObjectEditContext(payload.key, context, payload.selectionRef), context));
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
case "get-class-name-source": {
|
|
@@ -379,6 +379,9 @@ function isRequestPayload(payload, options) {
|
|
|
379
379
|
case "get-style-context":
|
|
380
380
|
return compositionOk;
|
|
381
381
|
case "get-object-edit-context":
|
|
382
|
+
return (typeof payload.key === "string" &&
|
|
383
|
+
compositionOk &&
|
|
384
|
+
isSelectionRefLike(payload.selectionRef));
|
|
382
385
|
case "get-class-name-source":
|
|
383
386
|
case "resolve-source-location":
|
|
384
387
|
return typeof payload.key === "string" && compositionOk;
|
|
@@ -400,6 +403,31 @@ function isRequestPayload(payload, options) {
|
|
|
400
403
|
(compositionOk && isMutationContextLike(payload.mutationContext)));
|
|
401
404
|
}
|
|
402
405
|
}
|
|
406
|
+
function isSelectionRefLike(value) {
|
|
407
|
+
if (value === undefined)
|
|
408
|
+
return true;
|
|
409
|
+
if (!isRecord(value) ||
|
|
410
|
+
typeof value.key !== "string" ||
|
|
411
|
+
value.key.length === 0) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const instanceIndex = value.instanceIndex;
|
|
415
|
+
const instanceCount = value.instanceCount;
|
|
416
|
+
if ((instanceIndex !== undefined &&
|
|
417
|
+
(typeof instanceIndex !== "number" ||
|
|
418
|
+
!Number.isInteger(instanceIndex) ||
|
|
419
|
+
instanceIndex < 0)) ||
|
|
420
|
+
(instanceCount !== undefined &&
|
|
421
|
+
(typeof instanceCount !== "number" ||
|
|
422
|
+
!Number.isInteger(instanceCount) ||
|
|
423
|
+
instanceCount <= 0)) ||
|
|
424
|
+
(typeof instanceIndex === "number" &&
|
|
425
|
+
typeof instanceCount === "number" &&
|
|
426
|
+
instanceIndex >= instanceCount)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return Object.keys(value).every((key) => key === "key" || key === "instanceIndex" || key === "instanceCount");
|
|
430
|
+
}
|
|
403
431
|
function isOperationRequestLike(value) {
|
|
404
432
|
return (isRecord(value) &&
|
|
405
433
|
typeof value.id === "string" &&
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import ft from"node:http";import x from"node:fs";import{createHash as Z,randomBytes as gt,timingSafeEqual as yt}from"node:crypto";import m from"node:path";import{WebSocket as X,WebSocketServer as It}from"ws";import{createPreviewSiteEditEngineRuntime as St,openFileHistoryStore as wt,resolveManagedPageTemplateComposition as Ct,scanSiteEditCompositionsFromFiles as Rt}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as xt,scanNextAppRouterRoutesFromFiles as Et}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as vt}from"@vcp-dev/site-edit-engine/source-watcher";function $(e){let t=new Map,s=[],i=new Map,o=[],n=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],r=new Map(n.map((u,h)=>[u,h])),a=e.pageComposition.status==="supported"?{status:"supported",containerKey:e.pageComposition.containerKey,hasBusinessMembers:e.pageComposition.hasBusinessMembers,placementUnitKeys:e.pageComposition.placementUnitKeys,placements:e.pageComposition.placements.map(u=>({placementKey:u.placementKey,placementUnitKey:u.placementUnitKey,relation:u.relation}))}:{status:"unsupported",reason:e.pageComposition.reason,diagnostics:e.pageComposition.diagnostics},c=e.entries.flatMap((u,h)=>u.dynamicDataSummary?[[h,u.dynamicDataSummary]]:[]),p=u=>{let h=t.get(u);if(typeof h=="number")return h;let C=s.length;return s.push(u),t.set(u,C),C},y=u=>{let h=null;for(let C of at(u)){let P=`${h??"root"}\0${C}`,N=i.get(P);if(typeof N=="number"){h=N;continue}let U=o.length;o.push([h,p(C)]),i.set(P,U),h=U}if(h===null)throw new Error("Document key must not be empty");return h};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:a,tokens:s,nodes:o,entries:e.entries.map(u=>{let h=u.pageCompositionUnitKey?r.get(u.pageCompositionUnitKey)??-1:-1;if(u.pageCompositionUnitKey&&h<0)throw new Error(`Render entry ${u.key} references unknown page composition unit ${u.pageCompositionUnitKey}`);return[y(u.key),rt(u.operationSummary),...h>=0?[h]:[]]}),...c.length>0?{dynamicDataSummaries:c}:{}}}function rt(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function at(e){if(!e)return[];let t=[],s=0,i=W(e,s);if(i<0)return t.push(e),t;for(t.push(e.slice(0,i)),s=i;s<e.length;){if(i=W(e,s+(e.startsWith("::",s)?2:1)),i<0){t.push(e.slice(s));break}t.push(e.slice(s,i)),s=i}return t}function W(e,t){let s=e.indexOf("/",t),i=e.indexOf("::",t);return s<0?i:i<0?s:Math.min(s,i)}var j="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",M="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var l=class e{constructor(){}static parseMessage(t,s={}){let i=JSON.parse(t);if(!e.isMessage(i,s))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(t){return JSON.stringify(t)}static extractRequestInfo(t){if(d(t)&&d(t.payload)){let s=typeof t.payload.id=="string"?t.payload.id:"unknown",i=this.isRequestKind(t.payload.kind)?t.payload.kind:"get-document";return{id:s,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==="undo"||t==="redo"}static isMessage(t,s={}){return!d(t)||typeof t.type!="string"?!1:t.type==="request"?ct(t.payload,s):t.type==="response"||t.type==="error"?d(t.payload)&&typeof t.payload.id=="string":t.type==="event"?d(t.payload)&&d(t.payload.event):t.type==="auth"?d(t.payload)&&typeof t.payload.key=="string":t.type==="auth-ok"&&d(t.payload)&&t.payload.ok===!0}},E=class{constructor(t){this.options=t,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new D,this.eventDispatcher=new A(s=>{this.sendJson(s)}),this.unsubscribe=t.engine.subscribe(s=>{this.handleEngineEvent(s)}),t.socket.onmessage=s=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(s.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 s=t;if(s.type!=="request")return;let i=s.payload,{id:o}=i;switch(i.kind){case"initialize-session":{let n=k(i),r=await this.resolve(n),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:g({...a,data:{...a.data,document:L(a.data.document)}},r.context)}});return}case"get-document":{let n=await this.resolveWireContext(i),r=await this.options.engine.getDocument(n);this.sendResult(o,i.kind,lt(r,n));return}case"get-history-status":{let n=await this.resolveWireContext(i),r=await this.options.engine.getHistoryStatus(n);this.sendResult(o,i.kind,g(r,n));return}case"get-style-context":{let n=await this.resolveWireContext(i);this.sendResult(o,i.kind,g(await this.options.engine.getStyleContext(n),n));return}case"get-object-edit-context":{let n=await this.resolveWireContext(i);this.sendResult(o,i.kind,g(await this.options.engine.getObjectEditContext(i.key,n),n));return}case"get-class-name-source":{let n=await this.resolveWireContext(i);this.sendResult(o,i.kind,g(await this.options.engine.getClassNameSource(i.key,n),n));return}case"get-source-tree":{let n=await this.resolveWireContext(i);this.sendResult(o,i.kind,g(await this.options.engine.getSourceTree(n),n));return}case"get-source-file":{let n=await this.resolveWireContext(i);this.sendResult(o,i.kind,g(await this.options.engine.getSourceFile({path:i.path,composition:n}),n));return}case"resolve-source-location":{let n=await this.resolveWireContext(i),r=g(await this.options.engine.resolveSourceLocation({key:i.key,composition:n}),n);this.sendResult(o,i.kind,r);return}case"execute":{let n=await this.resolveOperationWireContext(i.request),r=await this.resolveWireContext(i);if(I(n)!==I(r))throw new S(j,"The execute request composition does not match its operation composition.");let a=await this.normalizeOperationRequest(i.request,n);await this.handleBufferedOperation(o,i.kind,()=>this.options.engine.execute(a));return}case"undo":case"redo":{let n=await this.resolveWireContext(i);await this.handleBufferedOperation(o,i.kind,()=>i.kind==="undo"?this.options.engine.undo(n):this.options.engine.redo(n));return}}}catch(s){let i=l.extractRequestInfo(t);this.sendJson({type:"error",payload:{...i,message:O(s),code:T(s)}})}}async resolve(t){return this.options.engine.resolveComposition(t)}async resolveAndAssert(t){let s=await this.resolve(t),i=this.session.activeComposition;if(!i)throw new S(M,"Initialize a composition session before issuing this request.");if(I(i)!==I(s.context))throw new S(j,"The request composition does not match the active socket session.");return s.context}async resolveWireContext(t){let s=k(t);return this.resolveAndAssert(s)}async resolveOperationWireContext(t){let s=ht(t);return this.resolveAndAssert(s)}activateSession(t){this.session.activeComposition=t,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(I(t))}async normalizeOperationRequest(t,s){let i={...t,composition:z(s)};if(i.target.kind==="route"){let o=i.target,n=k(o),r=await this.resolveAndAssert(n);i.target={...o,composition:z(r)}}return i}handleEngineEvent(t){let s=t?.composition;if(!dt(s))return;let i=I(s);this.session.subscribedCompositionKeys.has(i)&&(this.eventDispatcher.push(t),t.type==="site-edit.graph.invalidated"&&i===pt(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(i)))}sendResult(t,s,i){this.sendJson({type:"response",payload:{id:t,kind:s,result:i}})}async handleRawMessage(t){try{await this.handleMessage(l.parseMessage(t,{validateOperationRequest:this.options.validateOperationRequest}))}catch(s){let i=mt(t);this.sendJson({type:"error",payload:{...i,message:O(s),code:T(s)}})}}sendJson(t){this.options.socket.send(l.stringifyMessage(t))}async handleBufferedOperation(t,s,i){this.eventDispatcher.beginBuffering();try{let o=await i();this.sendResult(t,s,o)}catch(o){this.sendJson({type:"error",payload:{id:t,kind:s,message:O(o),code:T(o)}})}finally{this.eventDispatcher.flush()}}},S=class extends Error{constructor(t,s){super(s),this.code=t,this.name="BridgeProtocolError"}},A=class{constructor(t){this.send=t,this.bufferedEvents=null}push(t){let s={type:"event",payload:{event:t}};if(this.bufferedEvents){this.bufferedEvents.push(s);return}this.send(s)}beginBuffering(){this.bufferedEvents=[]}flush(){let t=this.bufferedEvents??[];this.bufferedEvents=null,t.forEach(s=>this.send(s))}},D=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 s=async()=>{this.queued-=1,await t()},i=this.queue.then(s,s);return this.queue=i.then(()=>{},()=>{}),i}};function ct(e,t){if(!d(e)||typeof e.id!="string"||!l.isRequestKind(e.kind))return!1;let s=R(e.composition);switch(e.kind){case"initialize-session":return s&&(e.mutationContext===void 0||_(e.mutationContext));case"get-document":case"get-style-context":return s;case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&s;case"get-history-status":return s;case"get-source-tree":return s;case"get-source-file":return typeof e.path=="string"&&s;case"execute":return s&&ut(e.request)&&(e.request.mutationContext===void 0||_(e.request.mutationContext))&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return s&&e.mutationContext===void 0||s&&_(e.mutationContext)}}function ut(e){return d(e)&&typeof e.id=="string"&&typeof e.siteId=="string"&&typeof e.baseSnapshotId=="string"&&R(e.composition)&&typeof e.kind=="string"&&d(e.target)&&(e.target.kind!=="route"||R(e.target.composition))&&typeof e.documentVersion=="number"&&Number.isInteger(e.documentVersion)&&e.documentVersion>=0&&d(e.params)}function R(e){return d(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 dt(e){return d(e)&&typeof e.routeId=="string"&&(e.effectiveTemplateId===void 0||typeof e.effectiveTemplateId=="string")}function k(e){if(R(e.composition))return e.composition;throw new S(M,"A composition selector is required for this request.")}function ht(e){if(d(e)&&R(e.composition))return e.composition;throw new S(M,"A composition selector is required for execute.")}function z(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function pt(e){return e.activeComposition?I(e.activeComposition):null}function I(e){return JSON.stringify([e.routeId,e.effectiveTemplateId??null])}function g(e,t){return d(e)&&e.ok===!0?{...e,composition:t}:e}function lt(e,t){return!d(e)||e.ok!==!0?e:{...e,...t?{composition:t}:{},data:L(e.data)}}function L(e){let t=e;return $(t.routeId?t:{...t,routeId:t.composition.routeId})}function d(e){return typeof e=="object"&&e!==null}function _(e){return d(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 T(e){if(!(!d(e)||typeof e.code!="string"||e.code.length===0))return e.code}function O(e){return e instanceof Error||d(e)&&typeof e.message=="string"&&e.message.length>0?e.message:"Unknown websocket operation failure"}function mt(e){try{return l.extractRequestInfo(JSON.parse(e))}catch{return{id:"unknown",kind:"get-document"}}}var v=class{constructor(){this.tail=Promise.resolve()}run(t){let s=this.tail.then(t,t);return this.tail=s.then(()=>{},()=>{}),s}};import{SiteEditOperationRequestSchema as bt,computeSiteEditCompositionKey as w}from"@vcp-dev/contracts";var kt="127.0.0.1",_t=30218,Tt="/__vcp/site-edit/bridge",tt=6*1024*1024,Ot=tt*2,At=3e3,J=50,K=".vcp/current-snapshot.json",Q="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function re(e){let t=et(e),s=it(t),i=st(),o=i.get(s);if(o)return o;let n=new b(t);i.set(s,n);try{n.start()}catch(r){throw i.delete(s),n.stop(),r}return n}function ae(e,t,s={}){let i=new b(et(t),e,!1,s);return i.start({listen:!1}),i}var b=class{constructor(t,s=ft.createServer((n,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),i=!0,o={}){this.options=t,this.server=s,this.closeHttpServerOnStop=i,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new v,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=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()}start(t={}){if(this.isStarted)return;this.stopping=!1;try{x.mkdirSync(m.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(i){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${i instanceof Error?i.message:String(i)}`)}this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime();let s=Kt({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=s.stop,this.stopWatching=new vt({projectRoot:this.options.projectRoot,watch_dirs:Dt(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??J,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:i=>this.handleFileChanged(i),on_batch_change:i=>this.handleFilesChanged(i)}).start(),t.listen!==!1&&this.server.listen(this.options.port,this.options.host)}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=st(),s=it(this.options);t.get(s)===this&&t.delete(s),this.isStarted=!1}createRuntime(t,s=null){let i=this.runtime;this.activeHistoryStore=t??null;let o=(this.dependencies.createRuntime??St)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:n=>{"version"in n&&typeof n.version=="number"&&this.recordRuntimeVersion(n.version,"composition"in n?n.composition:void 0);for(let r of this.eventListeners)r(n)},...t?{historyStore:t}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=s,i?.dispose()}createBridgeEngine(){return{resolveComposition:t=>this.resolveComposition(t),initializeSession:t=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)throw new Error(`${s.code}: ${s.message}`);let i=Ft(t.composition),o=this.getRuntime(),n=f(i);await o.initializeComposition(n);let a=o.getDocument(n).data;await this.recordRuntimeVersion(a.version,i);let c=(await o.getHistoryStatus()).data;return{ok:!0,composition:i,data:{document:a,historyStatus:c}}}),getDocument:async t=>{let s=this.getRuntime(),i=f(t);await s.initializeComposition(i);let o=s.getDocument(i);return await this.recordRuntimeVersion(o.data.version,t),{...o,composition:t}},getHistoryStatus:async t=>({...await this.getRuntime().getHistoryStatus(),composition:t}),getStyleContext:async t=>this.getRuntime().getStyleContext(f(t)),getObjectEditContext:async(t,s)=>this.getRuntime().getObjectEditContext(t,f(s)),getClassNameSource:async(t,s)=>this.getRuntime().getClassNameSource(t,f(s)),getSourceTree:async t=>this.getRuntime().getSourceTree(f(t)),getSourceFile:async t=>this.getRuntime().getSourceFile({path:t.path,composition:f(t.composition)}),resolveSourceLocation:async t=>this.getRuntime().resolveSourceLocation({key:t.key,composition:f(t.composition)}),execute:(t,s)=>this.mutationQueue.run(async()=>{let i=await this.bindHistorySession(this.options.snapshotId);if(!i.ok)return F(i,t);let o=await this.getRuntime().executeOperation(t);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o}),undo:t=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return F(s,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 s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return F(s,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))}}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 s=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===t)return;let i=await(this.dependencies.openHistoryStore??wt)(Bt({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:t}));if(!this.isStarted)return;let o=i.readDocumentVersion?await i.readDocumentVersion():0,n=this.compositions[0]?.context;if(n){let r=w(n);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(i,t)});this.historySessionTransition=s.catch(()=>{});try{return await s,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(t,s){let i=s?w(s):null;i&&this.runtimeVersionFloors.set(i,Math.max(this.runtimeVersionFloors.get(i)??0,t));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let n=i?this.runtimeVersionFloors.get(i)??t:t;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(n)).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 s=this.options.runtime.watch_debounce_ms??J;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},s),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let t=[...this.pendingChangedFiles].sort((s,i)=>s.localeCompare(i));if(this.pendingChangedFiles.clear(),t.some($t)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let s of t)this.runtime?.notifyFileChanged(s)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let t=this.readCurrentSnapshotMarker();!t||t===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(t)).catch(s=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${s instanceof Error?s.message:String(s)}`)})}readCurrentSnapshotMarker(){try{let t=x.readFileSync(m.join(this.options.projectRoot,K),"utf8"),s=JSON.parse(t),i=typeof s.snapshot_id=="string"?s.snapshot_id:"";return!i||H(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 s=this.options.snapshotId;this.options.snapshotId=t;try{if(this.options.historyRoot){let o=await this.bindHistorySession(t);if(!o.ok)throw new Error(`${o.code}: ${o.message}`)}else this.createRuntime()}catch(o){this.options.snapshotId=s,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${s}: ${o instanceof Error?o.message:String(o)}`);return}let i=new Date().toISOString();for(let o of this.compositions){let n={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:t,previousSnapshotId:s,at:i};for(let r of this.eventListeners)try{r(n)}catch(a){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${a instanceof Error?a.message:String(a)}`)}}try{this.options.onSnapshotIdChanged?.(t,s)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(t=!1){try{let s=Mt(this.options.projectRoot),i=Et(s),o=i.length>0?i:(this.options.runtime.routes??[]).map(c=>({...c})),n=Rt({files:s});if(n.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=new Map(this.compositions.map(c=>[w(c.context),c])),a=new Map(n.map(c=>[w(c.context),c]));if(this.runtime){for(let[c,p]of r){let y=a.get(c);(!y||y.entryFile!==p.entryFile)&&this.runtime.unregisterComposition(f(p.context))}for(let[c,p]of a){let y=r.get(c);(!y||y.entryFile!==p.entryFile)&&this.runtime.registerComposition(p.context,p.entryFile)}}return this.routes=o,this.compositions=n,this.compositionFiles=s,this.compositionResolutionError=null,!0}catch(s){let i=s instanceof Error?s:new Error(String(s));if(this.compositionResolutionError=i,t)throw i;return!1}}resolveComposition(t){if(this.compositionResolutionError)throw this.compositionResolutionError;let s=xt(t.routeId,this.routes,this.options.basePath);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let i=this.routes.find(r=>r.routeId===s);if(!i)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let o=Ct({selector:{routeId:s,...t.templateId?{templateId:t.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:i.entryFile}),n=w(o.context);if(!this.compositions.some(r=>w(r.context)===n))throw new Error(`Composition is not registered by the preview bridge: ${n}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(t=>{let s=this.runtimeVersionFloors.get(w(t.context));return s===void 0?[]:[{composition:t.context,version:s}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function et(e){return{tenantId:e.tenantId,siteId:e.siteId,snapshotId:e.snapshotId,projectRoot:m.resolve(e.projectRoot??process.cwd()),basePath:e.basePath??"",host:e.host??kt,port:e.port??_t,path:e.path??Tt,sessionKey:e.sessionKey??Wt(),historyRoot:e.historyRoot,allowedOrigins:e.allowedOrigins??[],runtime:e.runtime??{},onSnapshotIdChanged:e.onSnapshotIdChanged}}function Dt(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 Mt(e){let t=[],s=["app","src/app",".vcp"],i=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=n=>{if(!x.existsSync(n))return;let r;try{r=x.readdirSync(n,{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(n,a.name));continue}if(!a.isFile())continue;let c=m.join(n,a.name),p="";try{p=x.readFileSync(c,"utf8")}catch{}t.push({path:m.relative(e,c).replaceAll(m.sep,"/"),content:p})}};for(let n of s)o(m.join(e,n));return t.sort((n,r)=>n.path.localeCompare(r.path))}function Ft(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 f(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function Bt(e){return m.join(m.resolve(e.root),H(e.siteId),H(e.snapshotId),Z("sha256").update(e.editingSessionId).digest("hex").slice(0,32))}function H(e){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e)&&e!=="."&&e!==".."?e:Z("sha256").update(e).digest("hex").slice(0,32)}function st(){let e=globalThis;return e[Q]??=new Map,e[Q]}function it(e){return`${e.projectRoot}:${e.host}:${e.port}:${e.path}:${e.historyRoot??""}`}function F(e,t,s){let i=s??(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 Kt(e){let t=new It({maxPayload:tt,noServer:!0}),s=new WeakSet,i=n=>{new E({socket:new q(n),engine:e.engine,validateOperationRequest:r=>bt.safeParse(r).success})},o=(n,r,a)=>{if(!Ht(n,e))return;let c=qt(n,e);if(!c){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import gt from"node:http";import x from"node:fs";import{createHash as Z,randomBytes as yt,timingSafeEqual as It}from"node:crypto";import m from"node:path";import{WebSocket as X,WebSocketServer as St}from"ws";import{createPreviewSiteEditEngineRuntime as wt,openFileHistoryStore as Ct,resolveManagedPageTemplateComposition as Rt,scanSiteEditCompositionsFromFiles as xt}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as Et,scanNextAppRouterRoutesFromFiles as bt}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as vt}from"@vcp-dev/site-edit-engine/source-watcher";function $(e){let t=new Map,s=[],n=new Map,o=[],i=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],r=new Map(i.map((u,h)=>[u,h])),a=e.pageComposition.status==="supported"?{status:"supported",containerKey:e.pageComposition.containerKey,hasBusinessMembers:e.pageComposition.hasBusinessMembers,placementUnitKeys:e.pageComposition.placementUnitKeys,placements:e.pageComposition.placements.map(u=>({placementKey:u.placementKey,placementUnitKey:u.placementUnitKey,relation:u.relation}))}:{status:"unsupported",reason:e.pageComposition.reason,diagnostics:e.pageComposition.diagnostics},c=e.entries.flatMap((u,h)=>u.dynamicDataSummary?[[h,u.dynamicDataSummary]]:[]),p=u=>{let h=t.get(u);if(typeof h=="number")return h;let C=s.length;return s.push(u),t.set(u,C),C},y=u=>{let h=null;for(let C of at(u)){let N=`${h??"root"}\0${C}`,P=n.get(N);if(typeof P=="number"){h=P;continue}let U=o.length;o.push([h,p(C)]),n.set(N,U),h=U}if(h===null)throw new Error("Document key must not be empty");return h};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:a,tokens:s,nodes:o,entries:e.entries.map(u=>{let h=u.pageCompositionUnitKey?r.get(u.pageCompositionUnitKey)??-1:-1;if(u.pageCompositionUnitKey&&h<0)throw new Error(`Render entry ${u.key} references unknown page composition unit ${u.pageCompositionUnitKey}`);return[y(u.key),rt(u.operationSummary),...h>=0?[h]:[]]}),...c.length>0?{dynamicDataSummaries:c}:{}}}function rt(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function at(e){if(!e)return[];let t=[],s=0,n=W(e,s);if(n<0)return t.push(e),t;for(t.push(e.slice(0,n)),s=n;s<e.length;){if(n=W(e,s+(e.startsWith("::",s)?2:1)),n<0){t.push(e.slice(s));break}t.push(e.slice(s,n)),s=n}return t}function W(e,t){let s=e.indexOf("/",t),n=e.indexOf("::",t);return s<0?n:n<0?s:Math.min(s,n)}var j="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",M="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var l=class e{constructor(){}static parseMessage(t,s={}){let n=JSON.parse(t);if(!e.isMessage(n,s))throw new Error("Invalid site edit bridge message");return n}static stringifyMessage(t){return JSON.stringify(t)}static extractRequestInfo(t){if(d(t)&&d(t.payload)){let s=typeof t.payload.id=="string"?t.payload.id:"unknown",n=this.isRequestKind(t.payload.kind)?t.payload.kind:"get-document";return{id:s,kind:n}}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==="undo"||t==="redo"}static isMessage(t,s={}){return!d(t)||typeof t.type!="string"?!1:t.type==="request"?ct(t.payload,s):t.type==="response"||t.type==="error"?d(t.payload)&&typeof t.payload.id=="string":t.type==="event"?d(t.payload)&&d(t.payload.event):t.type==="auth"?d(t.payload)&&typeof t.payload.key=="string":t.type==="auth-ok"&&d(t.payload)&&t.payload.ok===!0}},E=class{constructor(t){this.options=t,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new D,this.eventDispatcher=new A(s=>{this.sendJson(s)}),this.unsubscribe=t.engine.subscribe(s=>{this.handleEngineEvent(s)}),t.socket.onmessage=s=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(s.data))}catch(n){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:n instanceof Error?n.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 s=t;if(s.type!=="request")return;let n=s.payload,{id:o}=n;switch(n.kind){case"initialize-session":{let i=k(n),r=await this.resolve(i),a=await this.options.engine.initializeSession({composition:r.context,...n.mutationContext?{mutationContext:n.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:n.kind,result:g({...a,data:{...a.data,document:L(a.data.document)}},r.context)}});return}case"get-document":{let i=await this.resolveWireContext(n),r=await this.options.engine.getDocument(i);this.sendResult(o,n.kind,mt(r,i));return}case"get-history-status":{let i=await this.resolveWireContext(n),r=await this.options.engine.getHistoryStatus(i);this.sendResult(o,n.kind,g(r,i));return}case"get-style-context":{let i=await this.resolveWireContext(n);this.sendResult(o,n.kind,g(await this.options.engine.getStyleContext(i),i));return}case"get-object-edit-context":{let i=await this.resolveWireContext(n);this.sendResult(o,n.kind,g(await this.options.engine.getObjectEditContext(n.key,i,n.selectionRef),i));return}case"get-class-name-source":{let i=await this.resolveWireContext(n);this.sendResult(o,n.kind,g(await this.options.engine.getClassNameSource(n.key,i),i));return}case"get-source-tree":{let i=await this.resolveWireContext(n);this.sendResult(o,n.kind,g(await this.options.engine.getSourceTree(i),i));return}case"get-source-file":{let i=await this.resolveWireContext(n);this.sendResult(o,n.kind,g(await this.options.engine.getSourceFile({path:n.path,composition:i}),i));return}case"resolve-source-location":{let i=await this.resolveWireContext(n),r=g(await this.options.engine.resolveSourceLocation({key:n.key,composition:i}),i);this.sendResult(o,n.kind,r);return}case"execute":{let i=await this.resolveOperationWireContext(n.request),r=await this.resolveWireContext(n);if(I(i)!==I(r))throw new S(j,"The execute request composition does not match its operation composition.");let a=await this.normalizeOperationRequest(n.request,i);await this.handleBufferedOperation(o,n.kind,()=>this.options.engine.execute(a));return}case"undo":case"redo":{let i=await this.resolveWireContext(n);await this.handleBufferedOperation(o,n.kind,()=>n.kind==="undo"?this.options.engine.undo(i):this.options.engine.redo(i));return}}}catch(s){let n=l.extractRequestInfo(t);this.sendJson({type:"error",payload:{...n,message:O(s),code:T(s)}})}}async resolve(t){return this.options.engine.resolveComposition(t)}async resolveAndAssert(t){let s=await this.resolve(t),n=this.session.activeComposition;if(!n)throw new S(M,"Initialize a composition session before issuing this request.");if(I(n)!==I(s.context))throw new S(j,"The request composition does not match the active socket session.");return s.context}async resolveWireContext(t){let s=k(t);return this.resolveAndAssert(s)}async resolveOperationWireContext(t){let s=pt(t);return this.resolveAndAssert(s)}activateSession(t){this.session.activeComposition=t,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(I(t))}async normalizeOperationRequest(t,s){let n={...t,composition:z(s)};if(n.target.kind==="route"){let o=n.target,i=k(o),r=await this.resolveAndAssert(i);n.target={...o,composition:z(r)}}return n}handleEngineEvent(t){let s=t?.composition;if(!ht(s))return;let n=I(s);this.session.subscribedCompositionKeys.has(n)&&(this.eventDispatcher.push(t),t.type==="site-edit.graph.invalidated"&&n===lt(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(n)))}sendResult(t,s,n){this.sendJson({type:"response",payload:{id:t,kind:s,result:n}})}async handleRawMessage(t){try{await this.handleMessage(l.parseMessage(t,{validateOperationRequest:this.options.validateOperationRequest}))}catch(s){let n=ft(t);this.sendJson({type:"error",payload:{...n,message:O(s),code:T(s)}})}}sendJson(t){this.options.socket.send(l.stringifyMessage(t))}async handleBufferedOperation(t,s,n){this.eventDispatcher.beginBuffering();try{let o=await n();this.sendResult(t,s,o)}catch(o){this.sendJson({type:"error",payload:{id:t,kind:s,message:O(o),code:T(o)}})}finally{this.eventDispatcher.flush()}}},S=class extends Error{constructor(t,s){super(s),this.code=t,this.name="BridgeProtocolError"}},A=class{constructor(t){this.send=t,this.bufferedEvents=null}push(t){let s={type:"event",payload:{event:t}};if(this.bufferedEvents){this.bufferedEvents.push(s);return}this.send(s)}beginBuffering(){this.bufferedEvents=[]}flush(){let t=this.bufferedEvents??[];this.bufferedEvents=null,t.forEach(s=>this.send(s))}},D=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 s=async()=>{this.queued-=1,await t()},n=this.queue.then(s,s);return this.queue=n.then(()=>{},()=>{}),n}};function ct(e,t){if(!d(e)||typeof e.id!="string"||!l.isRequestKind(e.kind))return!1;let s=R(e.composition);switch(e.kind){case"initialize-session":return s&&(e.mutationContext===void 0||_(e.mutationContext));case"get-document":case"get-style-context":return s;case"get-object-edit-context":return typeof e.key=="string"&&s&&ut(e.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&s;case"get-history-status":return s;case"get-source-tree":return s;case"get-source-file":return typeof e.path=="string"&&s;case"execute":return s&&dt(e.request)&&(e.request.mutationContext===void 0||_(e.request.mutationContext))&&(t.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return s&&e.mutationContext===void 0||s&&_(e.mutationContext)}}function ut(e){if(e===void 0)return!0;if(!d(e)||typeof e.key!="string"||e.key.length===0)return!1;let t=e.instanceIndex,s=e.instanceCount;return t!==void 0&&(typeof t!="number"||!Number.isInteger(t)||t<0)||s!==void 0&&(typeof s!="number"||!Number.isInteger(s)||s<=0)||typeof t=="number"&&typeof s=="number"&&t>=s?!1:Object.keys(e).every(n=>n==="key"||n==="instanceIndex"||n==="instanceCount")}function dt(e){return d(e)&&typeof e.id=="string"&&typeof e.siteId=="string"&&typeof e.baseSnapshotId=="string"&&R(e.composition)&&typeof e.kind=="string"&&d(e.target)&&(e.target.kind!=="route"||R(e.target.composition))&&typeof e.documentVersion=="number"&&Number.isInteger(e.documentVersion)&&e.documentVersion>=0&&d(e.params)}function R(e){return d(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 ht(e){return d(e)&&typeof e.routeId=="string"&&(e.effectiveTemplateId===void 0||typeof e.effectiveTemplateId=="string")}function k(e){if(R(e.composition))return e.composition;throw new S(M,"A composition selector is required for this request.")}function pt(e){if(d(e)&&R(e.composition))return e.composition;throw new S(M,"A composition selector is required for execute.")}function z(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function lt(e){return e.activeComposition?I(e.activeComposition):null}function I(e){return JSON.stringify([e.routeId,e.effectiveTemplateId??null])}function g(e,t){return d(e)&&e.ok===!0?{...e,composition:t}:e}function mt(e,t){return!d(e)||e.ok!==!0?e:{...e,...t?{composition:t}:{},data:L(e.data)}}function L(e){let t=e;return $(t.routeId?t:{...t,routeId:t.composition.routeId})}function d(e){return typeof e=="object"&&e!==null}function _(e){return d(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 T(e){if(!(!d(e)||typeof e.code!="string"||e.code.length===0))return e.code}function O(e){return e instanceof Error||d(e)&&typeof e.message=="string"&&e.message.length>0?e.message:"Unknown websocket operation failure"}function ft(e){try{return l.extractRequestInfo(JSON.parse(e))}catch{return{id:"unknown",kind:"get-document"}}}var b=class{constructor(){this.tail=Promise.resolve()}run(t){let s=this.tail.then(t,t);return this.tail=s.then(()=>{},()=>{}),s}};import{SiteEditOperationRequestSchema as kt,computeSiteEditCompositionKey as w}from"@vcp-dev/contracts";var _t="127.0.0.1",Tt=30218,Ot="/__vcp/site-edit/bridge",tt=6*1024*1024,At=tt*2,Dt=3e3,J=50,K=".vcp/current-snapshot.json",Q="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function ae(e){let t=et(e),s=nt(t),n=st(),o=n.get(s);if(o)return o;let i=new v(t);n.set(s,i);try{i.start()}catch(r){throw n.delete(s),i.stop(),r}return i}function ce(e,t,s={}){let n=new v(et(t),e,!1,s);return n.start({listen:!1}),n}var v=class{constructor(t,s=gt.createServer((i,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),n=!0,o={}){this.options=t,this.server=s,this.closeHttpServerOnStop=n,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new b,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=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()}start(t={}){if(this.isStarted)return;this.stopping=!1;try{x.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)}`)}this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime();let s=Ht({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=s.stop,this.stopWatching=new vt({projectRoot:this.options.projectRoot,watch_dirs:Mt(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??J,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)}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=st(),s=nt(this.options);t.get(s)===this&&t.delete(s),this.isStarted=!1}createRuntime(t,s=null){let n=this.runtime;this.activeHistoryStore=t??null;let o=(this.dependencies.createRuntime??wt)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:i=>{"version"in i&&typeof i.version=="number"&&this.recordRuntimeVersion(i.version,"composition"in i?i.composition:void 0);for(let r of this.eventListeners)r(i)},...t?{historyStore:t}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=s,n?.dispose()}createBridgeEngine(){return{resolveComposition:t=>this.resolveComposition(t),initializeSession:t=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)throw new Error(`${s.code}: ${s.message}`);let n=Bt(t.composition),o=this.getRuntime(),i=f(n);await o.initializeComposition(i);let a=o.getDocument(i).data;await this.recordRuntimeVersion(a.version,n);let c=(await o.getHistoryStatus()).data;return{ok:!0,composition:n,data:{document:a,historyStatus:c}}}),getDocument:async t=>{let s=this.getRuntime(),n=f(t);await s.initializeComposition(n);let o=s.getDocument(n);return await this.recordRuntimeVersion(o.data.version,t),{...o,composition:t}},getHistoryStatus:async t=>({...await this.getRuntime().getHistoryStatus(),composition:t}),getStyleContext:async t=>this.getRuntime().getStyleContext(f(t)),getObjectEditContext:async(t,s,n)=>this.getRuntime().getObjectEditContext(t,f(s),n),getClassNameSource:async(t,s)=>this.getRuntime().getClassNameSource(t,f(s)),getSourceTree:async t=>this.getRuntime().getSourceTree(f(t)),getSourceFile:async t=>this.getRuntime().getSourceFile({path:t.path,composition:f(t.composition)}),resolveSourceLocation:async t=>this.getRuntime().resolveSourceLocation({key:t.key,composition:f(t.composition)}),execute:(t,s)=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return F(n,t);let o=await this.getRuntime().executeOperation(t);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o}),undo:t=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return F(s,void 0,t);let n=await this.getRuntime().undo();return n.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(n.data.resultVersion,n.composition??t),n.composition?n:{...n,composition:t}}),redo:t=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return F(s,void 0,t);let n=await this.getRuntime().redo();return n.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(n.data.resultVersion,n.composition??t),n.composition?n:{...n,composition:t}}),subscribe:t=>(this.eventListeners.add(t),()=>this.eventListeners.delete(t))}}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 s=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===t)return;let n=await(this.dependencies.openHistoryStore??Ct)(Kt({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:t}));if(!this.isStarted)return;let o=n.readDocumentVersion?await n.readDocumentVersion():0,i=this.compositions[0]?.context;if(i){let r=w(i);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(n,t)});this.historySessionTransition=s.catch(()=>{});try{return await s,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(t,s){let n=s?w(s):null;n&&this.runtimeVersionFloors.set(n,Math.max(this.runtimeVersionFloors.get(n)??0,t));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let i=n?this.runtimeVersionFloors.get(n)??t:t;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(i)).catch(()=>{}),this.versionPersistence}handleFileChanged(t){this.handleFilesChanged([t])}handleFilesChanged(t){for(let n of t)this.pendingChangedFiles.add(n);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let s=this.options.runtime.watch_debounce_ms??J;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},s),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let t=[...this.pendingChangedFiles].sort((s,n)=>s.localeCompare(n));if(this.pendingChangedFiles.clear(),t.some(jt)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let s of t)this.runtime?.notifyFileChanged(s)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let t=this.readCurrentSnapshotMarker();!t||t===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(t)).catch(s=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${s instanceof Error?s.message:String(s)}`)})}readCurrentSnapshotMarker(){try{let t=x.readFileSync(m.join(this.options.projectRoot,K),"utf8"),s=JSON.parse(t),n=typeof s.snapshot_id=="string"?s.snapshot_id:"";return!n||H(n)!==n?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):n}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 s=this.options.snapshotId;this.options.snapshotId=t;try{if(this.options.historyRoot){let o=await this.bindHistorySession(t);if(!o.ok)throw new Error(`${o.code}: ${o.message}`)}else this.createRuntime()}catch(o){this.options.snapshotId=s,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${s}: ${o instanceof Error?o.message:String(o)}`);return}let n=new Date().toISOString();for(let o of this.compositions){let i={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:t,previousSnapshotId:s,at:n};for(let r of this.eventListeners)try{r(i)}catch(a){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${a instanceof Error?a.message:String(a)}`)}}try{this.options.onSnapshotIdChanged?.(t,s)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(t=!1){try{let s=Ft(this.options.projectRoot),n=bt(s),o=n.length>0?n:(this.options.runtime.routes??[]).map(c=>({...c})),i=xt({files:s});if(i.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=new Map(this.compositions.map(c=>[w(c.context),c])),a=new Map(i.map(c=>[w(c.context),c]));if(this.runtime){for(let[c,p]of r){let y=a.get(c);(!y||y.entryFile!==p.entryFile)&&this.runtime.unregisterComposition(f(p.context))}for(let[c,p]of a){let y=r.get(c);(!y||y.entryFile!==p.entryFile)&&this.runtime.registerComposition(p.context,p.entryFile)}}return this.routes=o,this.compositions=i,this.compositionFiles=s,this.compositionResolutionError=null,!0}catch(s){let n=s instanceof Error?s:new Error(String(s));if(this.compositionResolutionError=n,t)throw n;return!1}}resolveComposition(t){if(this.compositionResolutionError)throw this.compositionResolutionError;let s=Et(t.routeId,this.routes,this.options.basePath);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let n=this.routes.find(r=>r.routeId===s);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${t.routeId}`);let o=Rt({selector:{routeId:s,...t.templateId?{templateId:t.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:n.entryFile}),i=w(o.context);if(!this.compositions.some(r=>w(r.context)===i))throw new Error(`Composition is not registered by the preview bridge: ${i}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(t=>{let s=this.runtimeVersionFloors.get(w(t.context));return s===void 0?[]:[{composition:t.context,version:s}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function et(e){return{tenantId:e.tenantId,siteId:e.siteId,snapshotId:e.snapshotId,projectRoot:m.resolve(e.projectRoot??process.cwd()),basePath:e.basePath??"",host:e.host??_t,port:e.port??Tt,path:e.path??Ot,sessionKey:e.sessionKey??$t(),historyRoot:e.historyRoot,allowedOrigins:e.allowedOrigins??[],runtime:e.runtime??{},onSnapshotIdChanged:e.onSnapshotIdChanged}}function Mt(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 Ft(e){let t=[],s=["app","src/app",".vcp"],n=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=i=>{if(!x.existsSync(i))return;let r;try{r=x.readdirSync(i,{withFileTypes:!0})}catch{return}for(let a of r){if(a.name.startsWith(".")&&a.name!==".vcp")continue;if(a.isDirectory()){if(n.has(a.name))continue;o(m.join(i,a.name));continue}if(!a.isFile())continue;let c=m.join(i,a.name),p="";try{p=x.readFileSync(c,"utf8")}catch{}t.push({path:m.relative(e,c).replaceAll(m.sep,"/"),content:p})}};for(let i of s)o(m.join(e,i));return t.sort((i,r)=>i.path.localeCompare(r.path))}function Bt(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 f(e){return{routeId:e.routeId,...e.effectiveTemplateId?{templateId:e.effectiveTemplateId}:{}}}function Kt(e){return m.join(m.resolve(e.root),H(e.siteId),H(e.snapshotId),Z("sha256").update(e.editingSessionId).digest("hex").slice(0,32))}function H(e){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e)&&e!=="."&&e!==".."?e:Z("sha256").update(e).digest("hex").slice(0,32)}function st(){let e=globalThis;return e[Q]??=new Map,e[Q]}function nt(e){return`${e.projectRoot}:${e.host}:${e.port}:${e.path}:${e.historyRoot??""}`}function F(e,t,s){let n=s??(t?.composition?{routeId:t.composition.routeId,...t.composition.templateId?{effectiveTemplateId:t.composition.templateId}:{}}:void 0);if(!n)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:n,error:{code:e.code,message:e.message},data:{requestId:t?.id??"mutation-context",kind:t?.kind??"update-text",composition:n,target:t?.target??{kind:"node",key:""},resultVersion:t?.documentVersion??0}}}function Ht(e){let t=new St({maxPayload:tt,noServer:!0}),s=new WeakSet,n=i=>{new E({socket:new q(i),engine:e.engine,validateOperationRequest:r=>kt.safeParse(r).success})},o=(i,r,a)=>{if(!qt(i,e))return;let c=Vt(i,e);if(!c){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),r.destroy();return}c.authenticatedByHeader&&s.add(
|
|
4
|
+
`),r.destroy();return}c.authenticatedByHeader&&s.add(i),t.handleUpgrade(i,r,a,p=>{t.emit("connection",p,i)})};return e.server.on("upgrade",o),t.on("connection",(i,r)=>{if(s.has(r)){n(i);return}Nt(i,e.sessionKey,()=>{n(i)})}),t.stop=()=>{e.server.off("upgrade",o);for(let i of t.clients)i.close(1001,"bridge stopped");t.close()},t}var q=class{constructor(t){this.socket=t,this.onmessage=null,this.onclose=null,t.on("error",()=>{}),t.on("message",s=>{this.onmessage?.({data:s.toString()})}),t.on("close",()=>{this.onclose?.()})}send(t){if(this.socket.readyState===X.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(t,"utf8")>At){this.socket.close(1013,"bridge backpressure");return}this.socket.send(t)}}close(t,s){this.socket.close(t,s)}};function qt(e,t){return new URL(e.url??"/","http://localhost").pathname===t.path}function Vt(e,t){if(new URL(e.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let n=B(e.headers["x-vcp-site-edit-key"]),o=it(n,t.sessionKey),i=B(e.headers.host),r=B(e.headers.origin);return i&&!Pt(i,t.host,r)?null:r?Wt(r,i,t.allowedOrigins)?{authenticatedByHeader:o}:null:o?{authenticatedByHeader:!0}:null}function Nt(e,t,s){let n=setTimeout(()=>{e.close(1008,"bridge authentication timeout")},Dt),o,i=()=>{clearTimeout(n),e.off("message",o),e.off("close",i)},r=()=>{i(),e.close(1008,"bridge authentication failed")};o=a=>{try{let c=l.parseMessage(a.toString());if(c.type!=="auth"||!it(c.payload.key,t)){r();return}i(),e.readyState===X.OPEN&&e.send(l.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),s()}catch{r()}},e.on("message",o),e.on("close",i)}function it(e,t){if(!e)return!1;let s=Buffer.from(e),n=Buffer.from(t);return s.length===n.length&&It(s,n)}function Pt(e,t,s){let n=Y(e),o=Y(t);return n===o||G(o)&&G(n)&&(!s||V(s,e))||Ut(o)&&(!s||V(s,e))}function Ut(e){return e==="0.0.0.0"||e==="::"}function G(e){return e==="localhost"||e==="127.0.0.1"||e==="::1"}function Wt(e,t,s){return s.includes(e)||(t?V(e,t):!1)}function V(e,t){try{return new URL(e).host===ot(t)}catch{return!1}}function Y(e){let t=ot(e);return t.startsWith("[")?t.slice(1,t.indexOf("]")):t.split(":").length-1===1?t.split(":",1)[0]??"":t}function ot(e){return e.trim().toLowerCase()}function B(e){return Array.isArray(e)?e[0]:e}function $t(){return yt(32).toString("base64url")}function jt(e){let t=e.replaceAll("\\","/");return t===K||t.endsWith(`/${K}`)}export{At as SITE_EDIT_BRIDGE_MAX_BUFFERED_BYTES,tt as SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES,v as SiteEditBridgeServer,ce as attachSiteEditBridgeServer,Ht as attachSiteEditWebSocketServer,ae as ensureSiteEditBridgeServerStarted};
|
package/dist/window-message.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SiteEditCommandSuccess, SiteEditClassNameSourceStyleContext, SiteEditCompositionContext, SiteEditCompositionSelector, SiteEditEvent, SiteEditObjectEditContext, SiteEditGetClassNameSourceCommandResult, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
|
|
2
|
+
import { type SiteEditOperationSelectionRef } from "@vcp-dev/contracts";
|
|
2
3
|
import type { SiteEditHistoryStatus } from "./protocol.js";
|
|
4
|
+
export { getPropertyPreviewDomAttributeName, planPropertyPreview, type SiteEditPropertyPreviewElement, type SiteEditPropertyPreviewInput, type SiteEditPropertyPreviewResult, } from "./property-preview.js";
|
|
3
5
|
export declare const VCP_WINDOW_MESSAGE_SOURCE = "vcp-window-message";
|
|
4
6
|
export declare const VCP_WINDOW_MESSAGE_VERSION = 1;
|
|
5
7
|
export type VcpWindowMessageEnvelope<TNamespace extends string = string, TChannel extends string = string, TBody = unknown> = {
|
|
@@ -309,7 +311,16 @@ export type SiteEditFrameRuntimeRequestBody = {
|
|
|
309
311
|
} | {
|
|
310
312
|
type: "request";
|
|
311
313
|
id: string;
|
|
312
|
-
kind: "bridge-get-object-edit-context"
|
|
314
|
+
kind: "bridge-get-object-edit-context";
|
|
315
|
+
payload: {
|
|
316
|
+
key: string;
|
|
317
|
+
composition: SiteEditCompositionSelector;
|
|
318
|
+
selectionRef?: SiteEditOperationSelectionRef;
|
|
319
|
+
};
|
|
320
|
+
} | {
|
|
321
|
+
type: "request";
|
|
322
|
+
id: string;
|
|
323
|
+
kind: "bridge-get-class-name-source";
|
|
313
324
|
payload: {
|
|
314
325
|
key: string;
|
|
315
326
|
composition: SiteEditCompositionSelector;
|