@abide/abide 0.28.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.
Files changed (562) hide show
  1. package/CHANGELOG.md +607 -0
  2. package/LICENSE +21 -0
  3. package/README.md +154 -0
  4. package/bin/abide.ts +212 -0
  5. package/package.json +155 -0
  6. package/src/abideLsp.ts +211 -0
  7. package/src/abideModules.d.ts +8 -0
  8. package/src/abideResolverPlugin.ts +923 -0
  9. package/src/appEntry.ts +151 -0
  10. package/src/assets/app.html +12 -0
  11. package/src/build.ts +143 -0
  12. package/src/buildCli.ts +127 -0
  13. package/src/buildDisconnected.ts +118 -0
  14. package/src/bundleApp.ts +147 -0
  15. package/src/bundleDisconnectedEntry.ts +14 -0
  16. package/src/checkAbide.ts +77 -0
  17. package/src/cliEntry.ts +25 -0
  18. package/src/clientBuildPlugins.ts +33 -0
  19. package/src/clientEntry.ts +17 -0
  20. package/src/compile.ts +63 -0
  21. package/src/controlServerWorker.ts +426 -0
  22. package/src/devEntry.ts +250 -0
  23. package/src/discoveryEntry.ts +81 -0
  24. package/src/lib/bundle/BundleMenu.ts +12 -0
  25. package/src/lib/bundle/BundleMenuItem.ts +25 -0
  26. package/src/lib/bundle/BundleWindow.ts +37 -0
  27. package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
  28. package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
  29. package/src/lib/bundle/bindConnectedFlag.ts +29 -0
  30. package/src/lib/bundle/bindRequestNavigate.ts +34 -0
  31. package/src/lib/bundle/buildWebviewLib.ts +111 -0
  32. package/src/lib/bundle/bundled.ts +35 -0
  33. package/src/lib/bundle/disconnected.abide +236 -0
  34. package/src/lib/bundle/disconnected.css +9 -0
  35. package/src/lib/bundle/ensureWebviewLib.ts +20 -0
  36. package/src/lib/bundle/exitWithParent.ts +28 -0
  37. package/src/lib/bundle/infoPlist.ts +46 -0
  38. package/src/lib/bundle/installDownloads.ts +24 -0
  39. package/src/lib/bundle/installMacMenu.ts +39 -0
  40. package/src/lib/bundle/listenLocalControlServer.ts +19 -0
  41. package/src/lib/bundle/native/abideMenu.mm +422 -0
  42. package/src/lib/bundle/native/webview.h +4557 -0
  43. package/src/lib/bundle/onMenu.ts +42 -0
  44. package/src/lib/bundle/openWebview.ts +104 -0
  45. package/src/lib/bundle/pngToIcns.ts +47 -0
  46. package/src/lib/bundle/probeAbideServer.ts +57 -0
  47. package/src/lib/bundle/resolveServerBinary.ts +12 -0
  48. package/src/lib/bundle/resolveWebviewLib.ts +53 -0
  49. package/src/lib/bundle/serverBinaryFilename.ts +8 -0
  50. package/src/lib/bundle/signMacApp.ts +37 -0
  51. package/src/lib/bundle/spawnEmbeddedServer.ts +64 -0
  52. package/src/lib/bundle/stableLocalPort.ts +19 -0
  53. package/src/lib/bundle/waitForServer.ts +23 -0
  54. package/src/lib/bundle/webviewCachePath.ts +23 -0
  55. package/src/lib/bundle/webviewLibName.ts +11 -0
  56. package/src/lib/cli/connectToServer.ts +23 -0
  57. package/src/lib/cli/createClient.ts +108 -0
  58. package/src/lib/cli/dispatchCommand.ts +71 -0
  59. package/src/lib/cli/loadEnvFromBinaryDir.ts +15 -0
  60. package/src/lib/cli/parseArgvForRpc.ts +100 -0
  61. package/src/lib/cli/printHelp.ts +119 -0
  62. package/src/lib/cli/printSessionHelp.ts +27 -0
  63. package/src/lib/cli/printSessionStatus.ts +21 -0
  64. package/src/lib/cli/printTrimmed.ts +8 -0
  65. package/src/lib/cli/printValue.ts +10 -0
  66. package/src/lib/cli/resolveCliTarget.ts +48 -0
  67. package/src/lib/cli/runCli.ts +176 -0
  68. package/src/lib/cli/runSession.ts +108 -0
  69. package/src/lib/cli/startLocalInstance.ts +14 -0
  70. package/src/lib/cli/tokenizeLine.ts +51 -0
  71. package/src/lib/cli/types/CliManifest.ts +9 -0
  72. package/src/lib/cli/types/CliManifestEntry.ts +17 -0
  73. package/src/lib/cli/types/CliTarget.ts +13 -0
  74. package/src/lib/mcp/annotationsForMethod.ts +29 -0
  75. package/src/lib/mcp/createMcpResourceServer.ts +102 -0
  76. package/src/lib/mcp/createMcpServer.ts +48 -0
  77. package/src/lib/mcp/dispatchMcpRequest.ts +138 -0
  78. package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
  79. package/src/lib/mcp/mcpSurface.ts +295 -0
  80. package/src/lib/mcp/toolResultFromResponse.ts +66 -0
  81. package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
  82. package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
  83. package/src/lib/mcp/types/McpResourceContents.ts +10 -0
  84. package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
  85. package/src/lib/mcp/types/McpResourceServer.ts +12 -0
  86. package/src/lib/mcp/types/McpServer.ts +9 -0
  87. package/src/lib/mcp/types/McpServerOptions.ts +16 -0
  88. package/src/lib/server/AppModule.ts +47 -0
  89. package/src/lib/server/DELETE.ts +10 -0
  90. package/src/lib/server/GET.ts +10 -0
  91. package/src/lib/server/HEAD.ts +10 -0
  92. package/src/lib/server/PATCH.ts +10 -0
  93. package/src/lib/server/POST.ts +10 -0
  94. package/src/lib/server/PUT.ts +10 -0
  95. package/src/lib/server/agent.ts +86 -0
  96. package/src/lib/server/appDataDir.ts +16 -0
  97. package/src/lib/server/cli/buildEnvContent.ts +19 -0
  98. package/src/lib/server/cli/createTarGz.ts +77 -0
  99. package/src/lib/server/cli/handleCliDownload.ts +150 -0
  100. package/src/lib/server/cli/handleCliInstall.ts +37 -0
  101. package/src/lib/server/cli/installScript.ts +31 -0
  102. package/src/lib/server/cli/maxSourceMtime.ts +26 -0
  103. package/src/lib/server/cookies.ts +30 -0
  104. package/src/lib/server/env.ts +51 -0
  105. package/src/lib/server/error.ts +73 -0
  106. package/src/lib/server/json.ts +42 -0
  107. package/src/lib/server/jsonl.ts +47 -0
  108. package/src/lib/server/prompts/definePrompt.ts +21 -0
  109. package/src/lib/server/prompts/promptRegistry.ts +9 -0
  110. package/src/lib/server/prompts/registerPrompt.ts +6 -0
  111. package/src/lib/server/prompts/renderPromptTemplate.ts +17 -0
  112. package/src/lib/server/prompts/types/Prompt.ts +13 -0
  113. package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
  114. package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
  115. package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
  116. package/src/lib/server/reachable.ts +45 -0
  117. package/src/lib/server/redirect.ts +43 -0
  118. package/src/lib/server/request.ts +19 -0
  119. package/src/lib/server/rpc/defineVerb.ts +210 -0
  120. package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
  121. package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
  122. package/src/lib/server/rpc/parseArgs.ts +127 -0
  123. package/src/lib/server/rpc/readBodyWithinLimit.ts +44 -0
  124. package/src/lib/server/rpc/registerVerb.ts +6 -0
  125. package/src/lib/server/rpc/runWithVerbTimeout.ts +49 -0
  126. package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
  127. package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
  128. package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
  129. package/src/lib/server/rpc/types/VerbHelper.ts +87 -0
  130. package/src/lib/server/rpc/types/VerbRegistryEntry.ts +35 -0
  131. package/src/lib/server/rpc/unprocessed.ts +14 -0
  132. package/src/lib/server/rpc/verbRegistry.ts +11 -0
  133. package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
  134. package/src/lib/server/runtime/DEV_READY_MESSAGE.ts +6 -0
  135. package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
  136. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +107 -0
  137. package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +16 -0
  138. package/src/lib/server/runtime/acceptsGzip.ts +24 -0
  139. package/src/lib/server/runtime/buildCacheSnapshot.ts +61 -0
  140. package/src/lib/server/runtime/buildHealthPayload.ts +34 -0
  141. package/src/lib/server/runtime/buildInspectorSurface.ts +37 -0
  142. package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
  143. package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
  144. package/src/lib/server/runtime/containsTraversal.ts +37 -0
  145. package/src/lib/server/runtime/createAppAssetServer.ts +76 -0
  146. package/src/lib/server/runtime/createAssetHeaderCache.ts +31 -0
  147. package/src/lib/server/runtime/createPublicAssetServer.ts +67 -0
  148. package/src/lib/server/runtime/createReachable.ts +109 -0
  149. package/src/lib/server/runtime/createRouteDispatcher.ts +127 -0
  150. package/src/lib/server/runtime/createServer.ts +674 -0
  151. package/src/lib/server/runtime/createUiPageRenderer.ts +181 -0
  152. package/src/lib/server/runtime/crossOriginForbidden.ts +17 -0
  153. package/src/lib/server/runtime/crossOriginGate.ts +29 -0
  154. package/src/lib/server/runtime/devClientFingerprint.ts +117 -0
  155. package/src/lib/server/runtime/devHotModuleResponse.ts +40 -0
  156. package/src/lib/server/runtime/devReloadResponse.ts +41 -0
  157. package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
  158. package/src/lib/server/runtime/envSchemaStore.ts +15 -0
  159. package/src/lib/server/runtime/findOpenPort.ts +21 -0
  160. package/src/lib/server/runtime/getActiveServer.ts +6 -0
  161. package/src/lib/server/runtime/globToPathSet.ts +29 -0
  162. package/src/lib/server/runtime/gzipResponse.ts +46 -0
  163. package/src/lib/server/runtime/inProcessServer.ts +20 -0
  164. package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
  165. package/src/lib/server/runtime/isCrossOriginRequest.ts +23 -0
  166. package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
  167. package/src/lib/server/runtime/logExposedSurfaces.ts +156 -0
  168. package/src/lib/server/runtime/maybeMountInspector.ts +97 -0
  169. package/src/lib/server/runtime/mimeForExtension.ts +14 -0
  170. package/src/lib/server/runtime/pageUrlFromStore.ts +15 -0
  171. package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
  172. package/src/lib/server/runtime/parsePort.ts +11 -0
  173. package/src/lib/server/runtime/registryManifests.ts +66 -0
  174. package/src/lib/server/runtime/requestContext.ts +5 -0
  175. package/src/lib/server/runtime/resolvePageSnapshot.ts +25 -0
  176. package/src/lib/server/runtime/respondWithEmbeddedAsset.ts +18 -0
  177. package/src/lib/server/runtime/runWithRequestScope.ts +150 -0
  178. package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
  179. package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
  180. package/src/lib/server/runtime/serverSlot.ts +13 -0
  181. package/src/lib/server/runtime/setActiveServer.ts +6 -0
  182. package/src/lib/server/runtime/snapshotEntryFromCache.ts +83 -0
  183. package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
  184. package/src/lib/server/runtime/streamFromIterator.ts +86 -0
  185. package/src/lib/server/runtime/types/Assets.ts +6 -0
  186. package/src/lib/server/runtime/types/DevReloadStamp.ts +18 -0
  187. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +24 -0
  188. package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +11 -0
  189. package/src/lib/server/runtime/types/InspectorContext.ts +30 -0
  190. package/src/lib/server/runtime/types/InspectorSocket.ts +17 -0
  191. package/src/lib/server/runtime/types/InspectorSurface.ts +13 -0
  192. package/src/lib/server/runtime/types/InspectorVerb.ts +27 -0
  193. package/src/lib/server/runtime/types/RequestStore.ts +55 -0
  194. package/src/lib/server/runtime/warnUnguardedMcp.ts +32 -0
  195. package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
  196. package/src/lib/server/server.ts +33 -0
  197. package/src/lib/server/socket.ts +32 -0
  198. package/src/lib/server/sockets/createSocketDispatcher.ts +337 -0
  199. package/src/lib/server/sockets/defineSocket.ts +179 -0
  200. package/src/lib/server/sockets/lookupSocket.ts +6 -0
  201. package/src/lib/server/sockets/registerSocket.ts +6 -0
  202. package/src/lib/server/sockets/socketOperations.ts +36 -0
  203. package/src/lib/server/sockets/socketRegistry.ts +9 -0
  204. package/src/lib/server/sockets/types/Socket.ts +23 -0
  205. package/src/lib/server/sockets/types/SocketClientFrame.ts +19 -0
  206. package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
  207. package/src/lib/server/sockets/types/SocketOptions.ts +26 -0
  208. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +19 -0
  209. package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
  210. package/src/lib/server/sockets/types/SocketServerFrame.ts +24 -0
  211. package/src/lib/server/sse.ts +54 -0
  212. package/src/lib/shared/ABIDE_PACKAGE_NAME.ts +7 -0
  213. package/src/lib/shared/ABIDE_VERSION.ts +9 -0
  214. package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
  215. package/src/lib/shared/CACHE_WRAPPED.ts +8 -0
  216. package/src/lib/shared/CLI_PATH.ts +7 -0
  217. package/src/lib/shared/DEV_HOT_PREFIX.ts +7 -0
  218. package/src/lib/shared/DEV_RELOAD_PATH.ts +6 -0
  219. package/src/lib/shared/HEALTH_PATH.ts +7 -0
  220. package/src/lib/shared/HttpError.ts +20 -0
  221. package/src/lib/shared/IDENTITY_PATH.ts +6 -0
  222. package/src/lib/shared/INSPECTOR_PATH.ts +7 -0
  223. package/src/lib/shared/NAV_HEADER.ts +8 -0
  224. package/src/lib/shared/OFFLINE_HEADER.ts +8 -0
  225. package/src/lib/shared/REMOTE_FUNCTION.ts +8 -0
  226. package/src/lib/shared/REPLAYABLE_METHODS.ts +12 -0
  227. package/src/lib/shared/SOCKETS_PATH.ts +7 -0
  228. package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
  229. package/src/lib/shared/SocketDisconnectedError.ts +13 -0
  230. package/src/lib/shared/TEXT_PLAIN.ts +7 -0
  231. package/src/lib/shared/abideImportName.ts +44 -0
  232. package/src/lib/shared/abideLog.ts +38 -0
  233. package/src/lib/shared/activeCacheStore.ts +20 -0
  234. package/src/lib/shared/activePage.ts +25 -0
  235. package/src/lib/shared/appDataDir.ts +34 -0
  236. package/src/lib/shared/appNameSlot.ts +10 -0
  237. package/src/lib/shared/basePath.ts +10 -0
  238. package/src/lib/shared/basePathFromAppUrl.ts +20 -0
  239. package/src/lib/shared/baseSlot.ts +14 -0
  240. package/src/lib/shared/binaryDirEnvPath.ts +12 -0
  241. package/src/lib/shared/browserClientFlags.ts +10 -0
  242. package/src/lib/shared/buildRpcProxy.ts +39 -0
  243. package/src/lib/shared/buildRpcRequest.ts +70 -0
  244. package/src/lib/shared/buildSocketOverChannel.ts +58 -0
  245. package/src/lib/shared/bundleLayout.ts +36 -0
  246. package/src/lib/shared/cache.ts +951 -0
  247. package/src/lib/shared/cacheEntryFromSnapshot.ts +59 -0
  248. package/src/lib/shared/cacheStoreSlot.ts +16 -0
  249. package/src/lib/shared/cacheStores.ts +10 -0
  250. package/src/lib/shared/canonicalJson.ts +63 -0
  251. package/src/lib/shared/carriesBodyArgs.ts +13 -0
  252. package/src/lib/shared/clearLastConnection.ts +7 -0
  253. package/src/lib/shared/commandNameForUrl.ts +17 -0
  254. package/src/lib/shared/createCacheStore.ts +104 -0
  255. package/src/lib/shared/createChannelLog.ts +122 -0
  256. package/src/lib/shared/createLifecycleChannel.ts +56 -0
  257. package/src/lib/shared/createLivenessWatch.ts +118 -0
  258. package/src/lib/shared/createPushIterator.ts +127 -0
  259. package/src/lib/shared/createRemoteFunction.ts +122 -0
  260. package/src/lib/shared/createSubscriber.ts +55 -0
  261. package/src/lib/shared/createTraceContext.ts +21 -0
  262. package/src/lib/shared/dataDirEnvPath.ts +12 -0
  263. package/src/lib/shared/decodeResponse.ts +47 -0
  264. package/src/lib/shared/detectTarget.ts +27 -0
  265. package/src/lib/shared/detectVerbMethod.ts +17 -0
  266. package/src/lib/shared/emitLogRecord.ts +190 -0
  267. package/src/lib/shared/exeSuffix.ts +9 -0
  268. package/src/lib/shared/exitOnBuildFailure.ts +17 -0
  269. package/src/lib/shared/extraForwardHeaders.ts +16 -0
  270. package/src/lib/shared/fileStem.ts +9 -0
  271. package/src/lib/shared/findExportCallSite.ts +476 -0
  272. package/src/lib/shared/formatTraceparent.ts +6 -0
  273. package/src/lib/shared/forwardHeaders.ts +44 -0
  274. package/src/lib/shared/getRemoteMeta.ts +5 -0
  275. package/src/lib/shared/globalCacheStore.ts +15 -0
  276. package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
  277. package/src/lib/shared/health.ts +179 -0
  278. package/src/lib/shared/healthReadSlot.ts +11 -0
  279. package/src/lib/shared/healthSeedSlot.ts +12 -0
  280. package/src/lib/shared/html.ts +38 -0
  281. package/src/lib/shared/importNamesToStrip.ts +13 -0
  282. package/src/lib/shared/invalidateEvent.ts +11 -0
  283. package/src/lib/shared/invalidateTripwire.ts +40 -0
  284. package/src/lib/shared/isAbideHealthPayload.ts +11 -0
  285. package/src/lib/shared/isCompileTarget.ts +15 -0
  286. package/src/lib/shared/isDebugEnabled.ts +26 -0
  287. package/src/lib/shared/isDebugNegated.ts +19 -0
  288. package/src/lib/shared/isModuleNotFound.ts +16 -0
  289. package/src/lib/shared/isReadOnlyMethod.ts +14 -0
  290. package/src/lib/shared/isReplayableMethod.ts +7 -0
  291. package/src/lib/shared/isStreamingResponse.ts +11 -0
  292. package/src/lib/shared/isSubscribable.ts +15 -0
  293. package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
  294. package/src/lib/shared/jsonSchemaForSchema.ts +39 -0
  295. package/src/lib/shared/jsonlErrorFrame.ts +24 -0
  296. package/src/lib/shared/keyForRemoteCall.ts +29 -0
  297. package/src/lib/shared/keyMatchesPrefix.ts +9 -0
  298. package/src/lib/shared/lastConnectionPath.ts +7 -0
  299. package/src/lib/shared/layoutChainForRoute.ts +22 -0
  300. package/src/lib/shared/loadEnvFile.ts +17 -0
  301. package/src/lib/shared/loadEnvFromDataDir.ts +14 -0
  302. package/src/lib/shared/log.ts +24 -0
  303. package/src/lib/shared/logClosingRecord.ts +28 -0
  304. package/src/lib/shared/logTapSlot.ts +13 -0
  305. package/src/lib/shared/manifestModule.ts +39 -0
  306. package/src/lib/shared/matchesDebugPattern.ts +16 -0
  307. package/src/lib/shared/memoizeByKey.ts +32 -0
  308. package/src/lib/shared/normalizeTarget.ts +10 -0
  309. package/src/lib/shared/online.ts +51 -0
  310. package/src/lib/shared/page.ts +30 -0
  311. package/src/lib/shared/pageSlot.ts +17 -0
  312. package/src/lib/shared/pageUrlForFile.ts +14 -0
  313. package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
  314. package/src/lib/shared/parseDebugPatterns.ts +21 -0
  315. package/src/lib/shared/parseEnv.ts +30 -0
  316. package/src/lib/shared/parsePromptMarkdown.ts +35 -0
  317. package/src/lib/shared/parseRouteSegments.ts +22 -0
  318. package/src/lib/shared/parseTraceparent.ts +26 -0
  319. package/src/lib/shared/pending.ts +30 -0
  320. package/src/lib/shared/prepareRpcModule.ts +59 -0
  321. package/src/lib/shared/prepareSocketModule.ts +49 -0
  322. package/src/lib/shared/probeRegistries.ts +68 -0
  323. package/src/lib/shared/producerKey.ts +32 -0
  324. package/src/lib/shared/programNameForPackage.ts +14 -0
  325. package/src/lib/shared/promptNameForFile.ts +10 -0
  326. package/src/lib/shared/queryStringFromArgs.ts +27 -0
  327. package/src/lib/shared/randomHexId.ts +14 -0
  328. package/src/lib/shared/readEnvFile.ts +15 -0
  329. package/src/lib/shared/readLastConnection.ts +18 -0
  330. package/src/lib/shared/readPackageJson.ts +9 -0
  331. package/src/lib/shared/recordRemoteMeta.ts +5 -0
  332. package/src/lib/shared/refreshing.ts +31 -0
  333. package/src/lib/shared/remoteMetaStore.ts +16 -0
  334. package/src/lib/shared/requestScopeSlot.ts +15 -0
  335. package/src/lib/shared/resolveClientFlags.ts +20 -0
  336. package/src/lib/shared/responseErrorText.ts +9 -0
  337. package/src/lib/shared/rpcTimeoutSlot.ts +9 -0
  338. package/src/lib/shared/rpcUrlForFile.ts +19 -0
  339. package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
  340. package/src/lib/shared/selectorMatcher.ts +68 -0
  341. package/src/lib/shared/selectorPrefix.ts +39 -0
  342. package/src/lib/shared/serializeEnv.ts +18 -0
  343. package/src/lib/shared/setAppName.ts +5 -0
  344. package/src/lib/shared/setBaseResolver.ts +6 -0
  345. package/src/lib/shared/setCacheStoreResolver.ts +6 -0
  346. package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
  347. package/src/lib/shared/setPageResolver.ts +7 -0
  348. package/src/lib/shared/setRequestScopeResolver.ts +6 -0
  349. package/src/lib/shared/snippet.ts +25 -0
  350. package/src/lib/shared/socketNameForFile.ts +11 -0
  351. package/src/lib/shared/socketTapSlot.ts +12 -0
  352. package/src/lib/shared/sseErrorFrame.ts +29 -0
  353. package/src/lib/shared/streamResponse.ts +169 -0
  354. package/src/lib/shared/stripImport.ts +27 -0
  355. package/src/lib/shared/subscribableFromResponse.ts +51 -0
  356. package/src/lib/shared/tailProbeSlot.ts +16 -0
  357. package/src/lib/shared/toBunRoutePattern.ts +28 -0
  358. package/src/lib/shared/toScopeSet.ts +4 -0
  359. package/src/lib/shared/trace.ts +16 -0
  360. package/src/lib/shared/types/CacheEntry.ts +84 -0
  361. package/src/lib/shared/types/CacheInvalidation.ts +9 -0
  362. package/src/lib/shared/types/CacheOnContext.ts +25 -0
  363. package/src/lib/shared/types/CacheOptions.ts +39 -0
  364. package/src/lib/shared/types/CacheSelector.ts +17 -0
  365. package/src/lib/shared/types/CacheSnapshot.ts +16 -0
  366. package/src/lib/shared/types/CacheSnapshotEntry.ts +17 -0
  367. package/src/lib/shared/types/CacheStats.ts +13 -0
  368. package/src/lib/shared/types/CacheStore.ts +39 -0
  369. package/src/lib/shared/types/ChannelLog.ts +13 -0
  370. package/src/lib/shared/types/ClientFlags.ts +11 -0
  371. package/src/lib/shared/types/CompileTarget.ts +6 -0
  372. package/src/lib/shared/types/FrameworkLog.ts +13 -0
  373. package/src/lib/shared/types/HttpVerb.ts +1 -0
  374. package/src/lib/shared/types/LastConnection.ts +9 -0
  375. package/src/lib/shared/types/Log.ts +13 -0
  376. package/src/lib/shared/types/LogRecord.ts +42 -0
  377. package/src/lib/shared/types/LogVoice.ts +7 -0
  378. package/src/lib/shared/types/PageSnapshot.ts +14 -0
  379. package/src/lib/shared/types/PromptArgument.ts +12 -0
  380. package/src/lib/shared/types/RawRemoteFunction.ts +14 -0
  381. package/src/lib/shared/types/RemoteCallable.ts +12 -0
  382. package/src/lib/shared/types/RemoteFunction.ts +47 -0
  383. package/src/lib/shared/types/ReplayableMethod.ts +7 -0
  384. package/src/lib/shared/types/RequestScopeInfo.ts +16 -0
  385. package/src/lib/shared/types/RpcInvoker.ts +6 -0
  386. package/src/lib/shared/types/SocketChannel.ts +17 -0
  387. package/src/lib/shared/types/SocketSubCallbacks.ts +13 -0
  388. package/src/lib/shared/types/StandardSchemaV1.ts +56 -0
  389. package/src/lib/shared/types/StreamedResolution.ts +10 -0
  390. package/src/lib/shared/types/Subscribable.ts +26 -0
  391. package/src/lib/shared/types/TailHooks.ts +12 -0
  392. package/src/lib/shared/types/TailOptions.ts +10 -0
  393. package/src/lib/shared/types/TraceContext.ts +17 -0
  394. package/src/lib/shared/url.ts +118 -0
  395. package/src/lib/shared/withBase.ts +11 -0
  396. package/src/lib/shared/withBaseUrl.ts +17 -0
  397. package/src/lib/shared/withJsonSchema.ts +21 -0
  398. package/src/lib/shared/writeDts.ts +12 -0
  399. package/src/lib/shared/writeHealthDts.ts +36 -0
  400. package/src/lib/shared/writeLastConnection.ts +13 -0
  401. package/src/lib/shared/writePublicAssetsDts.ts +31 -0
  402. package/src/lib/shared/writeRoutesDts.ts +73 -0
  403. package/src/lib/shared/writeRpcDts.ts +49 -0
  404. package/src/lib/shared/writeTestRpcDts.ts +45 -0
  405. package/src/lib/shared/writeTestSocketsDts.ts +34 -0
  406. package/src/lib/test/assertAgentFrameConformance.ts +73 -0
  407. package/src/lib/test/createScriptedSurface.ts +45 -0
  408. package/src/lib/test/createTestApp.ts +203 -0
  409. package/src/lib/test/createTestSocketChannel.ts +142 -0
  410. package/src/lib/ui/README.md +86 -0
  411. package/src/lib/ui/compile/SSR_ESCAPE.ts +25 -0
  412. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +36 -0
  413. package/src/lib/ui/compile/VOID_TAGS.ts +21 -0
  414. package/src/lib/ui/compile/abideUiPlugin.ts +65 -0
  415. package/src/lib/ui/compile/analyzeComponent.ts +117 -0
  416. package/src/lib/ui/compile/assetModulesFile.ts +32 -0
  417. package/src/lib/ui/compile/branchElements.ts +50 -0
  418. package/src/lib/ui/compile/collectAbideDiagnostics.ts +59 -0
  419. package/src/lib/ui/compile/compileComponent.ts +20 -0
  420. package/src/lib/ui/compile/compileModule.ts +116 -0
  421. package/src/lib/ui/compile/compileSSR.ts +36 -0
  422. package/src/lib/ui/compile/compileShadow.ts +352 -0
  423. package/src/lib/ui/compile/createShadowLanguageService.ts +197 -0
  424. package/src/lib/ui/compile/createShadowProgram.ts +96 -0
  425. package/src/lib/ui/compile/decodeHtmlEntities.ts +49 -0
  426. package/src/lib/ui/compile/desugarSignals.ts +133 -0
  427. package/src/lib/ui/compile/escapeHtml.ts +15 -0
  428. package/src/lib/ui/compile/generateBuild.ts +638 -0
  429. package/src/lib/ui/compile/generateSSR.ts +380 -0
  430. package/src/lib/ui/compile/groupBindParts.ts +28 -0
  431. package/src/lib/ui/compile/hoistCells.ts +120 -0
  432. package/src/lib/ui/compile/loadShadowTsConfig.ts +31 -0
  433. package/src/lib/ui/compile/lowerDocAccess.ts +202 -0
  434. package/src/lib/ui/compile/nearestProjectRoot.ts +16 -0
  435. package/src/lib/ui/compile/parseTemplate.ts +396 -0
  436. package/src/lib/ui/compile/partitionSlots.ts +36 -0
  437. package/src/lib/ui/compile/prepareNestedScript.ts +42 -0
  438. package/src/lib/ui/compile/remapShadowDiagnostic.ts +30 -0
  439. package/src/lib/ui/compile/renameSignalRefs.ts +85 -0
  440. package/src/lib/ui/compile/resolveAbideImports.ts +29 -0
  441. package/src/lib/ui/compile/scopeCss.ts +115 -0
  442. package/src/lib/ui/compile/shadowNaming.ts +11 -0
  443. package/src/lib/ui/compile/sourceToShadowOffset.ts +24 -0
  444. package/src/lib/ui/compile/staticAttrValue.ts +13 -0
  445. package/src/lib/ui/compile/stripEffects.ts +32 -0
  446. package/src/lib/ui/compile/types/AbideDiagnostic.ts +14 -0
  447. package/src/lib/ui/compile/types/AnalyzedComponent.ts +25 -0
  448. package/src/lib/ui/compile/types/CompiledShadow.ts +15 -0
  449. package/src/lib/ui/compile/types/TemplateAttr.ts +16 -0
  450. package/src/lib/ui/compile/types/TemplateNode.ts +78 -0
  451. package/src/lib/ui/compile/types/TextPart.ts +8 -0
  452. package/src/lib/ui/derived.ts +28 -0
  453. package/src/lib/ui/doc.ts +15 -0
  454. package/src/lib/ui/dom/appendSnippet.ts +34 -0
  455. package/src/lib/ui/dom/appendStatic.ts +27 -0
  456. package/src/lib/ui/dom/appendText.ts +114 -0
  457. package/src/lib/ui/dom/applyResolved.ts +72 -0
  458. package/src/lib/ui/dom/attach.ts +20 -0
  459. package/src/lib/ui/dom/attr.ts +19 -0
  460. package/src/lib/ui/dom/awaitBlock.ts +224 -0
  461. package/src/lib/ui/dom/cloneStatic.ts +52 -0
  462. package/src/lib/ui/dom/each.ts +115 -0
  463. package/src/lib/ui/dom/eachAsync.ts +153 -0
  464. package/src/lib/ui/dom/hydrate.ts +35 -0
  465. package/src/lib/ui/dom/mount.ts +29 -0
  466. package/src/lib/ui/dom/mountChild.ts +33 -0
  467. package/src/lib/ui/dom/on.ts +15 -0
  468. package/src/lib/ui/dom/openChild.ts +22 -0
  469. package/src/lib/ui/dom/openRoot.ts +20 -0
  470. package/src/lib/ui/dom/switchBlock.ts +75 -0
  471. package/src/lib/ui/dom/text.ts +20 -0
  472. package/src/lib/ui/dom/tryBlock.ts +112 -0
  473. package/src/lib/ui/dom/types/EachRow.ts +3 -0
  474. package/src/lib/ui/dom/types/SwitchCase.ts +6 -0
  475. package/src/lib/ui/dom/when.ts +73 -0
  476. package/src/lib/ui/effect.ts +16 -0
  477. package/src/lib/ui/installHotBridge.ts +73 -0
  478. package/src/lib/ui/matchRoute.ts +89 -0
  479. package/src/lib/ui/navigate.ts +17 -0
  480. package/src/lib/ui/probeNavigation.ts +33 -0
  481. package/src/lib/ui/remoteProxy.ts +97 -0
  482. package/src/lib/ui/renderChain.ts +50 -0
  483. package/src/lib/ui/renderToStream.ts +104 -0
  484. package/src/lib/ui/router.ts +286 -0
  485. package/src/lib/ui/runtime/OUTLET_TAG.ts +8 -0
  486. package/src/lib/ui/runtime/OWNER.ts +8 -0
  487. package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +14 -0
  488. package/src/lib/ui/runtime/RENDER.ts +23 -0
  489. package/src/lib/ui/runtime/RESUME.ts +16 -0
  490. package/src/lib/ui/runtime/applyPatchToTree.ts +41 -0
  491. package/src/lib/ui/runtime/claimChild.ts +10 -0
  492. package/src/lib/ui/runtime/clientPage.ts +16 -0
  493. package/src/lib/ui/runtime/createComputedNode.ts +16 -0
  494. package/src/lib/ui/runtime/createDoc.ts +177 -0
  495. package/src/lib/ui/runtime/createEffectNode.ts +58 -0
  496. package/src/lib/ui/runtime/createSignalNode.ts +16 -0
  497. package/src/lib/ui/runtime/detachLink.ts +21 -0
  498. package/src/lib/ui/runtime/endTracking.ts +24 -0
  499. package/src/lib/ui/runtime/enterRenderPass.ts +12 -0
  500. package/src/lib/ui/runtime/exitRenderPass.ts +7 -0
  501. package/src/lib/ui/runtime/firstOutlet.ts +22 -0
  502. package/src/lib/ui/runtime/flushEffects.ts +17 -0
  503. package/src/lib/ui/runtime/hotInstances.ts +10 -0
  504. package/src/lib/ui/runtime/hotReloadEnabled.ts +8 -0
  505. package/src/lib/ui/runtime/hotReplace.ts +25 -0
  506. package/src/lib/ui/runtime/nextBlockId.ts +11 -0
  507. package/src/lib/ui/runtime/pathExists.ts +23 -0
  508. package/src/lib/ui/runtime/readNode.ts +17 -0
  509. package/src/lib/ui/runtime/registerHotInstance.ts +23 -0
  510. package/src/lib/ui/runtime/runNode.ts +28 -0
  511. package/src/lib/ui/runtime/runtimePath.ts +9 -0
  512. package/src/lib/ui/runtime/scope.ts +24 -0
  513. package/src/lib/ui/runtime/toTeardown.ts +26 -0
  514. package/src/lib/ui/runtime/track.ts +58 -0
  515. package/src/lib/ui/runtime/trigger.ts +44 -0
  516. package/src/lib/ui/runtime/types/Cell.ts +5 -0
  517. package/src/lib/ui/runtime/types/Derived.ts +3 -0
  518. package/src/lib/ui/runtime/types/Doc.ts +19 -0
  519. package/src/lib/ui/runtime/types/EffectResult.ts +10 -0
  520. package/src/lib/ui/runtime/types/HotInstance.ts +14 -0
  521. package/src/lib/ui/runtime/types/NavVerdict.ts +9 -0
  522. package/src/lib/ui/runtime/types/Patch.ts +11 -0
  523. package/src/lib/ui/runtime/types/ReactiveLink.ts +21 -0
  524. package/src/lib/ui/runtime/types/ReactiveNode.ts +25 -0
  525. package/src/lib/ui/runtime/types/Route.ts +8 -0
  526. package/src/lib/ui/runtime/types/RouteLoader.ts +7 -0
  527. package/src/lib/ui/runtime/types/SsrRender.ts +22 -0
  528. package/src/lib/ui/runtime/types/State.ts +3 -0
  529. package/src/lib/ui/runtime/types/Teardown.ts +5 -0
  530. package/src/lib/ui/runtime/types/UiComponent.ts +16 -0
  531. package/src/lib/ui/runtime/types/UiProps.ts +15 -0
  532. package/src/lib/ui/runtime/unlinkDeps.ts +20 -0
  533. package/src/lib/ui/runtime/untrack.ts +20 -0
  534. package/src/lib/ui/runtime/valueAtPath.ts +18 -0
  535. package/src/lib/ui/runtime/writeNode.ts +16 -0
  536. package/src/lib/ui/socketChannel.ts +227 -0
  537. package/src/lib/ui/socketProxy.ts +25 -0
  538. package/src/lib/ui/startClient.ts +58 -0
  539. package/src/lib/ui/state.ts +25 -0
  540. package/src/lib/ui/tail.ts +324 -0
  541. package/src/lib/ui/types/Layouts.ts +9 -0
  542. package/src/lib/ui/types/Pages.ts +8 -0
  543. package/src/preload.ts +19 -0
  544. package/src/scaffold.ts +153 -0
  545. package/src/serverBuildPlugins.ts +19 -0
  546. package/src/serverEntry.ts +95 -0
  547. package/template/bunfig.toml +4 -0
  548. package/template/package.json +18 -0
  549. package/template/src/app.ts +28 -0
  550. package/template/src/bundle/icon.png +0 -0
  551. package/template/src/cli/banner.txt +3 -0
  552. package/template/src/cli/footer.txt +1 -0
  553. package/template/src/server/config.ts +17 -0
  554. package/template/src/server/rpc/getHello.ts +36 -0
  555. package/template/src/ui/Layout.abide +19 -0
  556. package/template/src/ui/app.css +21 -0
  557. package/template/src/ui/app.html +24 -0
  558. package/template/src/ui/pages/about/page.abide +9 -0
  559. package/template/src/ui/pages/page.abide +22 -0
  560. package/template/test/app.test.ts +30 -0
  561. package/template/tsconfig.json +18 -0
  562. package/tsconfig.app.json +17 -0
@@ -0,0 +1,923 @@
1
+ // node:fs existsSync — Bun plugin onResolve is sync-only; Bun.file().exists() is async
2
+ import { existsSync, statSync } from 'node:fs'
3
+ import type { BunPlugin } from 'bun'
4
+ import { Glob } from 'bun'
5
+ import { abideImportName } from './lib/shared/abideImportName.ts'
6
+ import { abideLog } from './lib/shared/abideLog.ts'
7
+ import { fileStem } from './lib/shared/fileStem.ts'
8
+ import { jsonSchemaForPromptArguments } from './lib/shared/jsonSchemaForPromptArguments.ts'
9
+ import { manifestModule } from './lib/shared/manifestModule.ts'
10
+ import { pageUrlForFile } from './lib/shared/pageUrlForFile.ts'
11
+ import { parsePromptMarkdown } from './lib/shared/parsePromptMarkdown.ts'
12
+ import { prepareRpcModule } from './lib/shared/prepareRpcModule.ts'
13
+ import { prepareSocketModule } from './lib/shared/prepareSocketModule.ts'
14
+ import { programNameForPackage } from './lib/shared/programNameForPackage.ts'
15
+ import { promptNameForFile } from './lib/shared/promptNameForFile.ts'
16
+ import { readPackageJson } from './lib/shared/readPackageJson.ts'
17
+ import { rpcUrlForFile } from './lib/shared/rpcUrlForFile.ts'
18
+ import { socketNameForFile } from './lib/shared/socketNameForFile.ts'
19
+ import { writeHealthDts } from './lib/shared/writeHealthDts.ts'
20
+ import { writePublicAssetsDts } from './lib/shared/writePublicAssetsDts.ts'
21
+ import { writeRoutesDts } from './lib/shared/writeRoutesDts.ts'
22
+ import { writeRpcDts } from './lib/shared/writeRpcDts.ts'
23
+ import { writeTestRpcDts } from './lib/shared/writeTestRpcDts.ts'
24
+ import { writeTestSocketsDts } from './lib/shared/writeTestSocketsDts.ts'
25
+
26
+ /*
27
+ Resolves a bare directory or extensionless path to a concrete file. Mirrors
28
+ Node-style resolution (path.ts, path.js, path/index.ts, path/index.js) so
29
+ project code can use `$`-prefixed aliases like `$shared/foo/utils` that point
30
+ at directories with an index file. The (path → resolved) mapping is
31
+ deterministic per build, so cache it — every module that imports a `$shared`
32
+ alias hits this twice or more, and each call would otherwise do up to nine
33
+ filesystem stats.
34
+ */
35
+ const resolveExtensionCache = new Map<string, string>()
36
+ function resolveExtension(path: string): string {
37
+ const cached = resolveExtensionCache.get(path)
38
+ if (cached !== undefined) {
39
+ return cached
40
+ }
41
+ const resolved = resolveExtensionUncached(path)
42
+ resolveExtensionCache.set(path, resolved)
43
+ return resolved
44
+ }
45
+
46
+ const RESOLVE_EXTENSIONS = ['.ts', '.js', '.tsx', '.jsx']
47
+
48
+ function resolveExtensionUncached(path: string): string {
49
+ if (existsSync(path) && !statSync(path).isDirectory()) {
50
+ return path
51
+ }
52
+ for (const extension of RESOLVE_EXTENSIONS) {
53
+ if (existsSync(`${path}${extension}`)) {
54
+ return `${path}${extension}`
55
+ }
56
+ }
57
+ for (const extension of RESOLVE_EXTENSIONS) {
58
+ const indexPath = `${path}/index${extension}`
59
+ if (existsSync(indexPath)) {
60
+ return indexPath
61
+ }
62
+ }
63
+ return path
64
+ }
65
+
66
+ const NS = 'abide-virtual'
67
+
68
+ function escapeRegex(value: string): string {
69
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
70
+ }
71
+
72
+ /* Memoises a zero-arg async producer so repeat calls reuse the first in-flight promise. */
73
+ function once<T>(produce: () => Promise<T>): () => Promise<T> {
74
+ let promise: Promise<T> | undefined
75
+ return () => {
76
+ if (!promise) {
77
+ promise = produce()
78
+ }
79
+ return promise
80
+ }
81
+ }
82
+
83
+ /*
84
+ Bun plugin that wires every virtual import abide produces at build time:
85
+ - `abide:rpc` — { rpcUrl: () => import(rpc-module) } HTTP-verb manifest
86
+ - `abide:sockets` — { socketName: () => import(socket-module) } socket manifest
87
+ - `abide:pages` — { pageUrl: () => import(page.abide) } manifest
88
+ - `abide:prompts` — { promptName: () => import(prompt-module) } manifest
89
+ - `abide:app` — { init?, handle?, handleError? } from src/app.ts
90
+ - `abide:assets` — gzip-compressed chunk bytes embedded for standalone compile
91
+ - `abide:public-assets` — gzip-embedded src/ui/public files
92
+ - `abide:mcp-resources` — gzip-embedded src/mcp/resources files
93
+ - `abide:shell` — app.html content (custom or default)
94
+
95
+ Also rewrites modules under src/server/rpc and src/server/sockets:
96
+ - src/server/rpc/<file>.ts: each HTTP-verb export is bound to a runtime
97
+ implementation — defineVerb on the server, remoteProxy on the client.
98
+ - src/server/sockets/<file>.ts: each `socket(opts)` export is bound to
99
+ defineSocket on the server (with the socket name + opts) or
100
+ socketProxy on the client (name only — opts are server-side).
101
+ */
102
+ // @readme plumbing
103
+ export function abideResolverPlugin({
104
+ cwd = process.cwd(),
105
+ embedAssets = false,
106
+ target = 'server',
107
+ }: {
108
+ cwd?: string
109
+ embedAssets?: boolean
110
+ target?: 'server' | 'client'
111
+ } = {}): BunPlugin {
112
+ const serverDir = `${cwd}/src/server`
113
+ const uiDir = `${cwd}/src/ui`
114
+ const sharedDir = `${cwd}/src/shared`
115
+ const mcpDir = `${cwd}/src/mcp`
116
+ const cliDir = `${cwd}/src/cli`
117
+ const rpcDir = `${serverDir}/rpc`
118
+ const socketsDir = `${serverDir}/sockets`
119
+ const pagesDir = `${uiDir}/pages`
120
+ const publicDir = `${uiDir}/public`
121
+ const promptsDir = `${mcpDir}/prompts`
122
+ const resourcesDir = `${mcpDir}/resources`
123
+
124
+ /*
125
+ The bare specifier the project imports abide under (canonical
126
+ `abide` or a package alias). Resolved once from the project's
127
+ package.json and threaded into every generated module so the codegen's
128
+ imports resolve regardless of which install style the project uses.
129
+ */
130
+ const abideImportNameOnce = once(() => abideImportName(cwd))
131
+ /*
132
+ The whole-tree validation + per-leaf classification only needs to run
133
+ once per build. Memoise the promise so the virtual manifests
134
+ (rpc/sockets/pages/layouts) share a single scan instead of each one
135
+ re-globbing the trees. The shell read is memoised the same way so two
136
+ passes don't re-read app.html from disk.
137
+ */
138
+ const scanPagesOnce = once(() =>
139
+ scanPages(pagesDir).then(async (scan) => {
140
+ await writeRoutesDts({
141
+ cwd,
142
+ pageFiles: scan.pageFiles,
143
+ importName: await abideImportNameOnce(),
144
+ })
145
+ return scan
146
+ }),
147
+ )
148
+ const scanRpcOnce = once(() =>
149
+ scanDir(rpcDir, '**/*.ts').then(async (rpcFiles) => {
150
+ const importName = await abideImportNameOnce()
151
+ await writeRpcDts({ cwd, rpcDir, rpcFiles, importName })
152
+ /* Typed createTestApp `app.rpc.<verb>` surface. */
153
+ await writeTestRpcDts({ cwd, rpcFiles, importName })
154
+ return rpcFiles
155
+ }),
156
+ )
157
+ const scanSocketsOnce = once(() =>
158
+ scanDir(socketsDir, '**/*.ts').then(async (socketFiles) => {
159
+ /* Typed createTestApp `app.sockets.<name>` surface. */
160
+ await writeTestSocketsDts({
161
+ cwd,
162
+ socketFiles,
163
+ importName: await abideImportNameOnce(),
164
+ })
165
+ return socketFiles
166
+ }),
167
+ )
168
+ /* One write per build, from the abide:app loader (the seam that already knows whether src/app.ts exists). */
169
+ let healthDtsWritten: Promise<void> | undefined
170
+ const writeHealthDtsOnce = (hasAppModule: boolean): Promise<void> => {
171
+ healthDtsWritten ??= abideImportNameOnce().then((importName) =>
172
+ writeHealthDts({ cwd, hasAppModule, importName }),
173
+ )
174
+ return healthDtsWritten
175
+ }
176
+ /*
177
+ Globs public/ once per build and writes publicAssets.d.ts so url() can
178
+ autocomplete known assets — independent of embedding (runs in dev/start
179
+ too, where the files are read off disk). The public-assets virtual reuses
180
+ the returned list for its embed.
181
+ */
182
+ const scanPublicOnce = once(async () => {
183
+ const publicFiles = existsSync(publicDir)
184
+ ? await Array.fromAsync(new Glob('**/*').scan({ cwd: publicDir, onlyFiles: true }))
185
+ : []
186
+ await writePublicAssetsDts({ cwd, publicFiles, importName: await abideImportNameOnce() })
187
+ return publicFiles
188
+ })
189
+ const scanPromptsOnce = once(() => scanDir(promptsDir, '**/*.md'))
190
+ const loadShellOnce = once(() => loadShell(cwd))
191
+ /* Project package.json read once per build — three virtuals (cli-name,
192
+ app-info, mcp identity) derive fields from it. */
193
+ const readPackageJsonOnce = once(() => readPackageJson(cwd))
194
+
195
+ const rpcFilter = new RegExp(`^${escapeRegex(rpcDir)}/.*\\.ts$`)
196
+ const socketsFilter = new RegExp(`^${escapeRegex(socketsDir)}/.*\\.ts$`)
197
+ const promptsFilter = new RegExp(`^${escapeRegex(promptsDir)}/.*\\.md$`)
198
+
199
+ return {
200
+ name: 'abide-resolver',
201
+ setup(build) {
202
+ build.onResolve(
203
+ {
204
+ filter: /\/_virtual\/(rpc|sockets|prompts|pages|layouts|errors|app|config|mcp-resources|mcp|assets|public-assets|shell|app-info|cli-manifest|cli-name|cli-chrome|bundle-window|bundle-disconnected-component|bundle-disconnected)\.ts$/,
205
+ },
206
+ (args) => {
207
+ const name = fileStem(args.path)
208
+ if (!name) {
209
+ return undefined
210
+ }
211
+ return { path: `abide:${name}`, namespace: NS }
212
+ },
213
+ )
214
+
215
+ /*
216
+ User-facing aliases are the five top-level project directories.
217
+ Sub-paths fall out of them: `$server/rpc/getThing`,
218
+ `$ui/pages/...`, `$mcp/prompts/...`, `$mcp/resources/...`.
219
+ `lib/` is userland — projects declare their own lib aliases.
220
+ */
221
+ const dirAliases: Record<string, string> = {
222
+ $server: serverDir,
223
+ $ui: uiDir,
224
+ $shared: sharedDir,
225
+ $mcp: mcpDir,
226
+ $cli: cliDir,
227
+ }
228
+ for (const [alias, baseDir] of Object.entries(dirAliases)) {
229
+ build.onResolve({ filter: new RegExp(`^\\${alias}(\\/.*)?$`) }, (args) => {
230
+ const subpath = args.path.slice(alias.length)
231
+ return { path: resolveExtension(subpath ? `${baseDir}${subpath}` : baseDir) }
232
+ })
233
+ }
234
+
235
+ /*
236
+ Root-absolute url() references in stylesheets (e.g.
237
+ `url(/fonts/x.woff2)`) point at files served from public/ at the
238
+ site root at runtime, not at anything on disk at build time. Bun's
239
+ CSS bundler otherwise tries to resolve them against the project
240
+ root and fails the whole build. Mark them external so the literal
241
+ `/…` path survives into the emitted CSS, where
242
+ createPublicAssetServer serves it. Scoped to CSS importers: abide-ui
243
+ <style> blocks compile to injected JS strings and never reach the
244
+ CSS bundler, and abide's own absolute-path JS imports come from
245
+ .ts/virtual importers — neither is a `.css` importer, so both are
246
+ untouched. Relative url()s (`./x.png`) still resolve and bundle
247
+ normally.
248
+ */
249
+ build.onResolve({ filter: /^\// }, (args) => {
250
+ if (args.importer.endsWith('.css')) {
251
+ return { path: args.path, external: true }
252
+ }
253
+ return undefined
254
+ })
255
+
256
+ build.onLoad({ filter: rpcFilter }, async (args) => {
257
+ if (!args.path.startsWith(`${rpcDir}/`)) {
258
+ return undefined
259
+ }
260
+ const relativePath = args.path.slice(rpcDir.length + 1)
261
+ const source = await Bun.file(args.path).text()
262
+ const url = rpcUrlForFile(relativePath)
263
+ const importName = await abideImportNameOnce()
264
+ const prepared = prepareRpcModule(source, importName)
265
+ if (!prepared) {
266
+ throw new Error(
267
+ `[abide] src/server/rpc/${relativePath} has no \`export const <name> = <VERB>(...)\` — every $rpc module must declare exactly one remote function`,
268
+ )
269
+ }
270
+ const expectedName = fileStem(relativePath)
271
+ if (prepared.exportName !== expectedName) {
272
+ throw new Error(
273
+ `[abide] src/server/rpc/${relativePath} exports \`${prepared.exportName}\` but the filename expects \`${expectedName}\` — the export name must match the file's stem`,
274
+ )
275
+ }
276
+ /*
277
+ For the client bundle, replace the entire module source
278
+ with a single proxy stub so the handler body and any
279
+ server-only top-level imports never reach the browser.
280
+ The stub keeps the same export name the source declared,
281
+ so page imports resolve identically on both sides.
282
+ */
283
+ if (target === 'client') {
284
+ const contents = `import { remoteProxy as __abideRemoteProxy__ } from '${importName}/ui/remoteProxy';
285
+ export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.verb)}, ${JSON.stringify(url)});
286
+ `
287
+ return { contents, loader: 'ts' }
288
+ }
289
+ /*
290
+ Server target: strip the user's verb import, then rewrite
291
+ the `<VERB>(` call so the verb (from the identifier) and
292
+ the URL (from the file path) are threaded into the
293
+ runtime constructor — defineVerb. The user's handler body
294
+ stays intact between the parens; any generics on the call
295
+ are dropped (they carry no runtime info). Rewriting is
296
+ tokenizer-driven so `GET` mentions inside strings and
297
+ comments are left alone.
298
+ */
299
+ const banner = `import { defineVerb as __abideDefineVerb__ } from '${importName}/server/rpc/defineVerb';
300
+ `
301
+ return { contents: `${banner}${prepared.rewriteForServer(url)}`, loader: 'ts' }
302
+ })
303
+
304
+ build.onLoad({ filter: socketsFilter }, async (args) => {
305
+ if (!args.path.startsWith(`${socketsDir}/`)) {
306
+ return undefined
307
+ }
308
+ const relativePath = args.path.slice(socketsDir.length + 1)
309
+ const source = await Bun.file(args.path).text()
310
+ const name = socketNameForFile(relativePath)
311
+ const importName = await abideImportNameOnce()
312
+ const prepared = prepareSocketModule(source, importName)
313
+ if (!prepared) {
314
+ throw new Error(
315
+ `[abide] src/server/sockets/${relativePath} has no \`export const <name> = socket(...)\` — every $sockets module must declare exactly one socket`,
316
+ )
317
+ }
318
+ const expectedName = fileStem(relativePath)
319
+ if (prepared.exportName !== expectedName) {
320
+ throw new Error(
321
+ `[abide] src/server/sockets/${relativePath} exports \`${prepared.exportName}\` but the filename expects \`${expectedName}\` — the export name must match the file's stem`,
322
+ )
323
+ }
324
+ if (target === 'client') {
325
+ /*
326
+ Client bundle gets a name-only stub — opts (tail,
327
+ clientPublish) are server-side state and don't
328
+ affect the client's wire behaviour.
329
+ */
330
+ const contents = `import { socketProxy as __abideSocketProxy__ } from '${importName}/ui/socketProxy';
331
+ export const ${prepared.exportName} = __abideSocketProxy__(${JSON.stringify(name)});
332
+ `
333
+ return { contents, loader: 'ts' }
334
+ }
335
+ const banner = `import { defineSocket as __abideDefineSocket__ } from '${importName}/server/sockets/defineSocket';
336
+ `
337
+ return {
338
+ contents: `${banner}${prepared.rewriteForServer(name)}`,
339
+ loader: 'ts',
340
+ }
341
+ })
342
+
343
+ build.onLoad({ filter: promptsFilter }, async (args) => {
344
+ if (!args.path.startsWith(`${promptsDir}/`)) {
345
+ return undefined
346
+ }
347
+ /*
348
+ Prompts are MCP-only — no client-side counterpart. The
349
+ client bundle never imports a prompts module, but emit an
350
+ empty stub for the client target defensively so a stray
351
+ import can't drag the prompt body into the browser bundle.
352
+ */
353
+ if (target === 'client') {
354
+ return { contents: 'export {}', loader: 'ts' }
355
+ }
356
+ /*
357
+ Server target: a `.md` prompt is data, not code. Parse the
358
+ frontmatter (description + arguments) and body once, then
359
+ generate a module that registers the prompt via definePrompt
360
+ — the body is embedded as a string literal and the render
361
+ closure interpolates `{{name}}` placeholders at call time.
362
+ */
363
+ const relativePath = args.path.slice(promptsDir.length + 1)
364
+ const source = await Bun.file(args.path).text()
365
+ const name = promptNameForFile(relativePath)
366
+ const importName = await abideImportNameOnce()
367
+ const parsed = parsePromptMarkdown(source)
368
+ const jsonSchema = jsonSchemaForPromptArguments(parsed.arguments)
369
+ const optionLines = [
370
+ parsed.description
371
+ ? ` description: ${JSON.stringify(parsed.description)},`
372
+ : undefined,
373
+ jsonSchema ? ` jsonSchema: ${JSON.stringify(jsonSchema)},` : undefined,
374
+ ` render: (args) => __abideRenderPromptTemplate__(__template__, args),`,
375
+ ]
376
+ .filter((line) => line !== undefined)
377
+ .join('\n')
378
+ const contents = `import { definePrompt as __abideDefinePrompt__ } from '${importName}/server/prompts/definePrompt'
379
+ import { renderPromptTemplate as __abideRenderPromptTemplate__ } from '${importName}/server/prompts/renderPromptTemplate'
380
+ const __template__ = ${JSON.stringify(parsed.body)}
381
+ export const prompt = __abideDefinePrompt__(${JSON.stringify(name)}, {
382
+ ${optionLines}
383
+ })
384
+ `
385
+ return { contents, loader: 'ts' }
386
+ })
387
+
388
+ build.onLoad({ filter: /.*/, namespace: NS }, async (args) => {
389
+ if (args.path === 'abide:rpc') {
390
+ return manifestModule({
391
+ files: await scanRpcOnce(),
392
+ keyForFile: rpcUrlForFile,
393
+ importDir: rpcDir,
394
+ exportName: 'rpc',
395
+ })
396
+ }
397
+
398
+ if (args.path === 'abide:sockets') {
399
+ return manifestModule({
400
+ files: await scanSocketsOnce(),
401
+ keyForFile: socketNameForFile,
402
+ importDir: socketsDir,
403
+ exportName: 'sockets',
404
+ })
405
+ }
406
+
407
+ if (args.path === 'abide:prompts') {
408
+ return manifestModule({
409
+ files: await scanPromptsOnce(),
410
+ keyForFile: promptNameForFile,
411
+ importDir: promptsDir,
412
+ exportName: 'prompts',
413
+ label: 'prompt modules',
414
+ })
415
+ }
416
+
417
+ if (args.path === 'abide:pages') {
418
+ const { pageFiles } = await scanPagesOnce()
419
+ return manifestModule({
420
+ files: pageFiles,
421
+ keyForFile: pageUrlForFile,
422
+ importDir: pagesDir,
423
+ exportName: 'pages',
424
+ })
425
+ }
426
+
427
+ if (args.path === 'abide:layouts') {
428
+ const { layoutFiles } = await scanPagesOnce()
429
+ return manifestModule({
430
+ files: layoutFiles,
431
+ keyForFile: pageUrlForFile,
432
+ importDir: pagesDir,
433
+ exportName: 'layouts',
434
+ })
435
+ }
436
+
437
+ if (args.path === 'abide:errors') {
438
+ const { errorFiles } = await scanPagesOnce()
439
+ return manifestModule({
440
+ files: errorFiles,
441
+ keyForFile: pageUrlForFile,
442
+ importDir: pagesDir,
443
+ exportName: 'errors',
444
+ label: 'error pages',
445
+ })
446
+ }
447
+
448
+ if (args.path === 'abide:app') {
449
+ const userApp = `${cwd}/src/app.ts`
450
+ const hasAppModule = await Bun.file(userApp).exists()
451
+ /* health.d.ts keys the client health() read to the app hook's return type. */
452
+ await writeHealthDtsOnce(hasAppModule)
453
+ if (hasAppModule) {
454
+ abideLog.info('using custom src/app.ts')
455
+ return {
456
+ contents: `export * from ${JSON.stringify(userApp)}`,
457
+ loader: 'js',
458
+ }
459
+ }
460
+ return { contents: 'export {};', loader: 'js' }
461
+ }
462
+
463
+ if (args.path === 'abide:config') {
464
+ /*
465
+ Re-exports src/server/config.ts so serverEntry can eager-import
466
+ it at boot — running its `env(schema)` validation once the env
467
+ layers are merged, before the server starts. Optional: an empty
468
+ stub when absent, so an app with no config builds and boots the
469
+ same (it just reads Bun.env directly).
470
+ */
471
+ const userConfig = `${serverDir}/config.ts`
472
+ if (await Bun.file(userConfig).exists()) {
473
+ abideLog.info('using src/server/config.ts')
474
+ return {
475
+ contents: `export * from ${JSON.stringify(userConfig)}`,
476
+ loader: 'js',
477
+ }
478
+ }
479
+ return { contents: 'export {};', loader: 'js' }
480
+ }
481
+
482
+ if (args.path === 'abide:cli-manifest') {
483
+ /*
484
+ The CLI binary's bake-time manifest. Discovery (a
485
+ one-shot script the bundler runs separately) writes
486
+ `${cwd}/dist/cli-manifest.json` from the populated
487
+ verbRegistry; this virtual splices that JSON in as a
488
+ default-exported object. Empty manifest when the
489
+ discovery file is missing — the binary still works
490
+ but exposes no subcommands until the user runs the
491
+ full `abide cli` flow.
492
+ */
493
+ const manifestPath = `${cwd}/dist/cli-manifest.json`
494
+ if (!existsSync(manifestPath)) {
495
+ return { contents: 'export default {}', loader: 'js' }
496
+ }
497
+ const json = await Bun.file(manifestPath).text()
498
+ return { contents: `export default ${json}`, loader: 'js' }
499
+ }
500
+
501
+ if (args.path === 'abide:cli-name') {
502
+ /*
503
+ Program name shown in `<program> --help`. Reads the
504
+ project's package.json `name` field (scoped names keep
505
+ only the final segment), falling back to `app` when
506
+ missing.
507
+ */
508
+ const pkg = await readPackageJsonOnce()
509
+ const name = programNameForPackage(pkg?.name as string | undefined)
510
+ return { contents: `export default ${JSON.stringify(name)}`, loader: 'js' }
511
+ }
512
+
513
+ if (args.path === 'abide:bundle-window') {
514
+ /*
515
+ Optional bundle window config (title/size/menu) baked into
516
+ the bundled launcher. Re-exports the default from
517
+ src/bundle/window.ts when present; otherwise an empty
518
+ object so the launcher falls back to its defaults.
519
+ */
520
+ const userFile = `${cwd}/src/bundle/window.ts`
521
+ if (existsSync(userFile)) {
522
+ abideLog.info('using custom src/bundle/window.ts')
523
+ return {
524
+ contents: `export { default } from ${JSON.stringify(userFile)}`,
525
+ loader: 'js',
526
+ }
527
+ }
528
+ return { contents: 'export default {}', loader: 'js' }
529
+ }
530
+
531
+ if (args.path === 'abide:bundle-disconnected') {
532
+ /*
533
+ The connect screen HTML baked into the launcher. buildDisconnected
534
+ writes `${cwd}/dist/bundle-disconnected.html`; this virtual splices
535
+ it in as a string export. A minimal inline fallback keeps the
536
+ launcher buildable when the file is missing (the screen still loads,
537
+ just unstyled) — bundleApp always builds it first.
538
+ */
539
+ const htmlPath = `${cwd}/dist/bundle-disconnected.html`
540
+ if (!existsSync(htmlPath)) {
541
+ const fallback =
542
+ '<!doctype html><html><body><div id="app">abide</div></body></html>'
543
+ return {
544
+ contents: `export const disconnectedHtml = ${JSON.stringify(fallback)}`,
545
+ loader: 'js',
546
+ }
547
+ }
548
+ const html = await Bun.file(htmlPath).text()
549
+ return {
550
+ contents: `export const disconnectedHtml = ${JSON.stringify(html)}`,
551
+ loader: 'js',
552
+ }
553
+ }
554
+
555
+ if (args.path === 'abide:bundle-disconnected-component') {
556
+ /*
557
+ The abide-ui component the connect-screen build mounts: the project's
558
+ src/bundle/disconnected.abide override when present, otherwise the lib
559
+ default. Re-exports the default like abide:bundle-window; the abide-ui
560
+ `.abide` loader compiles the target either way.
561
+ */
562
+ const userFile = `${cwd}/src/bundle/disconnected.abide`
563
+ if (existsSync(userFile)) {
564
+ abideLog.info('using custom src/bundle/disconnected.abide')
565
+ return {
566
+ contents: `export { default } from ${JSON.stringify(userFile)}`,
567
+ loader: 'js',
568
+ }
569
+ }
570
+ const defaultFile = new URL('./lib/bundle/disconnected.abide', import.meta.url)
571
+ .pathname
572
+ return {
573
+ contents: `export { default } from ${JSON.stringify(defaultFile)}`,
574
+ loader: 'js',
575
+ }
576
+ }
577
+
578
+ if (args.path === 'abide:cli-chrome') {
579
+ /*
580
+ Optional CLI help chrome baked into the binary: src/cli/
581
+ banner.txt prints atop top-level help, footer.txt prints
582
+ below it. Missing files emit empty strings (no chrome).
583
+ Read as plain text, like abide:shell.
584
+ */
585
+ const readChrome = async (name: string) => {
586
+ const file = Bun.file(`${cliDir}/${name}`)
587
+ return (await file.exists()) ? await file.text() : ''
588
+ }
589
+ const [banner, footer] = await Promise.all([
590
+ readChrome('banner.txt'),
591
+ readChrome('footer.txt'),
592
+ ])
593
+ return {
594
+ contents: `export const banner = ${JSON.stringify(banner)}
595
+ export const footer = ${JSON.stringify(footer)}
596
+ `,
597
+ loader: 'js',
598
+ }
599
+ }
600
+
601
+ if (args.path === 'abide:app-info') {
602
+ /*
603
+ Project identity ({ name, version }) read from
604
+ package.json, surfaced in the OpenAPI document's `info`
605
+ block. Falls back to placeholder values when the file
606
+ is missing so the spec still emits.
607
+ */
608
+ const pkg = await readPackageJsonOnce()
609
+ const info = {
610
+ name: (pkg?.name as string | undefined) ?? 'app',
611
+ version: (pkg?.version as string | undefined) ?? '0.0.0',
612
+ }
613
+ return {
614
+ contents: `export const appInfo = ${JSON.stringify(info)}`,
615
+ loader: 'js',
616
+ }
617
+ }
618
+
619
+ if (args.path === 'abide:mcp') {
620
+ /*
621
+ The MCP server is fully framework-generated — tools from
622
+ the verb registry, prompts from src/mcp/prompts, resources
623
+ from src/mcp/resources. createMcpServer is internal; there
624
+ is no user-authored server module. Server identity comes
625
+ from package.json so the `mcp__<name>__*` permission prefix
626
+ is stable and app-specific; absent a name, createMcpServer
627
+ falls back to its own default.
628
+ */
629
+ const importName = await abideImportNameOnce()
630
+ const pkg = await readPackageJsonOnce()
631
+ /* JSON.stringify drops undefined keys, so an absent name/version
632
+ leaves createMcpServer to apply its own defaults. */
633
+ const identity = JSON.stringify({
634
+ name: pkg?.name as string | undefined,
635
+ version: pkg?.version as string | undefined,
636
+ })
637
+ return {
638
+ contents: `import { createMcpServer } from '${importName}/mcp/createMcpServer'\nexport default createMcpServer(${identity})\n`,
639
+ loader: 'js',
640
+ }
641
+ }
642
+
643
+ if (args.path === 'abide:assets') {
644
+ if (!embedAssets) {
645
+ return { contents: 'export const assets = undefined', loader: 'js' }
646
+ }
647
+ const appDir = `${cwd}/dist/_app`
648
+ const files = await Array.fromAsync(
649
+ new Glob('**/*.gz').scan({ cwd: appDir, onlyFiles: true }),
650
+ )
651
+ const contents = await embedGzipDir({
652
+ dir: appDir,
653
+ files,
654
+ keyFor: (file) => `/_app/${file.replace(/\.gz$/, '')}`,
655
+ precompressed: true,
656
+ exportName: 'assets',
657
+ label: 'gzip assets',
658
+ source: 'dist/_app/',
659
+ })
660
+ return { contents, loader: 'js' }
661
+ }
662
+
663
+ if (args.path === 'abide:public-assets') {
664
+ /*
665
+ Embeds every file under public/ (gzip level 9, paid
666
+ once at compile) keyed by its site-root path so the
667
+ standalone binary serves them without a public/ dir on
668
+ disk. Mirrors abide:assets. Empty/undefined when not
669
+ embedding (dev + `abide start` read public/ off disk).
670
+ */
671
+ // Globs public/ and writes publicAssets.d.ts every build; reuse the list to embed.
672
+ const files = await scanPublicOnce()
673
+ if (!embedAssets || files.length === 0) {
674
+ return {
675
+ contents: 'export const publicAssets = undefined',
676
+ loader: 'js',
677
+ }
678
+ }
679
+ const contents = await embedGzipDir({
680
+ dir: publicDir,
681
+ files,
682
+ keyFor: (file) => `/${file}`,
683
+ precompressed: false,
684
+ exportName: 'publicAssets',
685
+ label: 'public files',
686
+ source: 'public/',
687
+ })
688
+ return { contents, loader: 'js' }
689
+ }
690
+
691
+ if (args.path === 'abide:mcp-resources') {
692
+ /*
693
+ Embeds every file under src/mcp/resources/ (gzip level
694
+ 9) keyed by its path relative to that dir, so the
695
+ standalone binary serves MCP resources without the folder
696
+ on disk. Mirrors abide:public-assets. Undefined when not
697
+ embedding (dev + `abide start` read off disk).
698
+ */
699
+ if (!embedAssets || !existsSync(resourcesDir)) {
700
+ return {
701
+ contents: 'export const mcpResources = undefined',
702
+ loader: 'js',
703
+ }
704
+ }
705
+ const files = await Array.fromAsync(
706
+ new Glob('**/*').scan({ cwd: resourcesDir, onlyFiles: true }),
707
+ )
708
+ if (files.length === 0) {
709
+ return {
710
+ contents: 'export const mcpResources = undefined',
711
+ loader: 'js',
712
+ }
713
+ }
714
+ const contents = await embedGzipDir({
715
+ dir: resourcesDir,
716
+ files,
717
+ keyFor: (file) => file,
718
+ precompressed: false,
719
+ exportName: 'mcpResources',
720
+ label: 'mcp resources',
721
+ source: 'src/mcp/resources/',
722
+ })
723
+ return { contents, loader: 'js' }
724
+ }
725
+
726
+ if (args.path === 'abide:shell') {
727
+ const content = await loadShellOnce()
728
+ return {
729
+ contents: `export const shell = ${JSON.stringify(content)}`,
730
+ loader: 'js',
731
+ }
732
+ }
733
+
734
+ return undefined
735
+ })
736
+ },
737
+ }
738
+ }
739
+
740
+ /*
741
+ Encodes every file in `files` (relative to `dir`) into a base64 gzip map and
742
+ emits `export const <exportName> = { "<key>": _d("<base64>") }`. `keyFor` maps
743
+ a relative path to its lookup key; `precompressed` true means the files are
744
+ already `.gz` on disk (read + base64 as-is), false means compress here at
745
+ level 9. Shared by the abide:assets / abide:public-assets / abide:mcp-resources
746
+ virtuals, which differ only in source dir, key shape, and whether the inputs
747
+ are pre-compressed.
748
+ */
749
+ async function embedGzipDir({
750
+ dir,
751
+ files,
752
+ keyFor,
753
+ precompressed,
754
+ exportName,
755
+ label,
756
+ source,
757
+ }: {
758
+ dir: string
759
+ files: string[]
760
+ keyFor: (file: string) => string
761
+ precompressed: boolean
762
+ exportName: string
763
+ label: string
764
+ source: string
765
+ }): Promise<string> {
766
+ const encoded = await Promise.all(
767
+ files.map(async (file) => {
768
+ const raw = await Bun.file(`${dir}/${file}`).bytes()
769
+ const bytes = precompressed ? raw : Bun.gzipSync(raw, { level: 9 })
770
+ return {
771
+ line: ` ${JSON.stringify(keyFor(file))}: _d(${JSON.stringify(bytes.toBase64())}),`,
772
+ bytes: bytes.byteLength,
773
+ }
774
+ }),
775
+ )
776
+ const totalBytes = encoded.reduce((total, entry) => total + entry.bytes, 0)
777
+ const unit = precompressed ? 'KiB' : 'KiB gzip'
778
+ abideLog.info(
779
+ `embedded ${encoded.length} ${label} from ${source} (${(totalBytes / 1024).toFixed(1)} ${unit})`,
780
+ )
781
+ return `const _d = (s) => Uint8Array.fromBase64(s)
782
+ export const ${exportName} = {
783
+ ${encoded.map((entry) => entry.line).join('\n')}
784
+ }
785
+ `
786
+ }
787
+
788
+ type PagesScan = {
789
+ pageFiles: string[]
790
+ layoutFiles: string[]
791
+ errorFiles: string[]
792
+ }
793
+
794
+ /*
795
+ Walks src/ui/pages once and classifies each `.abide` leaf by filename: a
796
+ `page.abide` is a route (its URL is the folder path), a `layout.abide` is a
797
+ layout that wraps every page at or below its folder (keyed by the same folder
798
+ URL). Any other `.abide` file (a shared component) is ignored here — free to
799
+ live anywhere and be imported relatively. errorFiles stay empty — there is no
800
+ framework error resolution.
801
+ */
802
+ async function scanPages(pagesDir: string): Promise<PagesScan> {
803
+ if (!existsSync(pagesDir)) {
804
+ return { pageFiles: [], layoutFiles: [], errorFiles: [] }
805
+ }
806
+ const allFiles = await Array.fromAsync(new Glob('**/*.abide').scan({ cwd: pagesDir }))
807
+ const leafIs = (name: string) => (file: string) => (file.split('/').pop() ?? '') === name
808
+ return {
809
+ pageFiles: allFiles.filter(leafIs('page.abide')),
810
+ layoutFiles: allFiles.filter(leafIs('layout.abide')),
811
+ errorFiles: [],
812
+ }
813
+ }
814
+
815
+ /*
816
+ Walks one registry directory once: src/server/rpc (every `.ts` file is an
817
+ HTTP-verb rpc handler), src/server/sockets (each `.ts` file declares one
818
+ socket, loaded lazily on first sub/pub frame), or src/mcp/prompts (each `.md`
819
+ file declares one MCP prompt — frontmatter for metadata, body for the
820
+ template). Returns an empty list when the directory doesn't exist so an app
821
+ missing the folder builds the same.
822
+ */
823
+ async function scanDir(dir: string, pattern: string): Promise<string[]> {
824
+ if (!existsSync(dir)) {
825
+ return []
826
+ }
827
+ return await Array.fromAsync(new Glob(pattern).scan({ cwd: dir }))
828
+ }
829
+
830
+ /*
831
+ Picks `src/ui/app.html` when it exists, otherwise the bundled default
832
+ shell. Reads the file once per build so the resolver's two virtual passes share
833
+ a single disk hit. Rewrites the literal `/_app/client.js` and `/_app/client.css`
834
+ references to the hashed entry filenames emitted by the client build so the
835
+ entry bundles can be served with `immutable` cache headers like the chunks.
836
+ */
837
+ async function loadShell(cwd: string): Promise<string> {
838
+ const userShell = `${cwd}/src/ui/app.html`
839
+ const defaultShell = new URL('./assets/app.html', import.meta.url).pathname
840
+ const filepath = (await Bun.file(userShell).exists()) ? userShell : defaultShell
841
+ if (filepath === userShell) {
842
+ abideLog.info('using custom src/ui/app.html')
843
+ }
844
+ const content = await Bun.file(filepath).text()
845
+ return await rewriteHashedClientEntries(injectShellAssets(content), cwd)
846
+ }
847
+
848
+ /*
849
+ Injects the framework's client entry references so app.html stays a clean
850
+ template — page structure plus the SSR markers, no framework asset bookkeeping.
851
+ The css <link> lands before </head>, the module <script> before </body>. Each is
852
+ skipped when the shell already carries that reference, so a custom app.html that
853
+ still spells the tags out (or one already processed) doesn't get a duplicate.
854
+ rewriteHashedClientEntries then swaps both for the hashed entry filenames.
855
+ */
856
+ function injectShellAssets(shell: string): string {
857
+ let result = shell
858
+ if (!result.includes('/_app/client.css')) {
859
+ result = injectBeforeTag(
860
+ result,
861
+ '<link rel="stylesheet" href="/_app/client.css" />',
862
+ 'head',
863
+ 'src/ui/app.html has no </head>',
864
+ )
865
+ }
866
+ if (!result.includes('/_app/client.js')) {
867
+ result = injectBeforeTag(
868
+ result,
869
+ '<script type="module" src="/_app/client.js"></script>',
870
+ 'body',
871
+ 'src/ui/app.html has no </body>',
872
+ )
873
+ }
874
+ return result
875
+ }
876
+
877
+ /*
878
+ Inserts `snippet` before the shell's closing `</tag>` (case-insensitive, so an
879
+ uppercase or oddly-cased custom app.html still works). When the tag is absent
880
+ the asset would otherwise be silently dropped, leaving the page unstyled /
881
+ unhydrated; instead warn and append the snippet so it still ships.
882
+ */
883
+ function injectBeforeTag(
884
+ shell: string,
885
+ snippet: string,
886
+ tag: 'head' | 'body',
887
+ missingMessage: string,
888
+ ): string {
889
+ const closing = new RegExp(`</${tag}\\s*>`, 'i')
890
+ if (closing.test(shell)) {
891
+ return shell.replace(closing, (match) => `${snippet}\n${match}`)
892
+ }
893
+ abideLog.warn(`${missingMessage} — appending the reference at the end of the document`)
894
+ return `${shell}\n${snippet}`
895
+ }
896
+
897
+ /*
898
+ Scans `dist/_app/` for the hashed client entry filenames produced by
899
+ build.ts (e.g. `client-abc12345.js`, `client-abc12345.css`) and swaps the
900
+ shell's literal `/_app/client.js` and `/_app/client.css` references for
901
+ them. When the directory is missing (someone running the server before a
902
+ build) the shell is returned unchanged so the existing broken-asset
903
+ behaviour is preserved.
904
+ */
905
+ async function rewriteHashedClientEntries(shell: string, cwd: string): Promise<string> {
906
+ const appDir = `${cwd}/dist/_app`
907
+ if (!existsSync(appDir)) {
908
+ return shell
909
+ }
910
+ const entries = await Array.fromAsync(
911
+ new Glob('client-*').scan({ cwd: appDir, onlyFiles: true }),
912
+ )
913
+ const jsEntry = entries.find((file) => /^client-[a-z0-9]+\.js$/i.test(file))
914
+ const cssEntry = entries.find((file) => /^client-[a-z0-9]+\.css$/i.test(file))
915
+ let result = shell
916
+ if (jsEntry) {
917
+ result = result.replace('/_app/client.js', `/_app/${jsEntry}`)
918
+ }
919
+ if (cssEntry) {
920
+ result = result.replace('/_app/client.css', `/_app/${cssEntry}`)
921
+ }
922
+ return result
923
+ }