@belte/belte 0.19.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 (326) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/LICENSE +21 -0
  3. package/README.md +559 -0
  4. package/bin/belte.ts +183 -0
  5. package/package.json +110 -0
  6. package/src/App.svelte +31 -0
  7. package/src/appEntry.ts +151 -0
  8. package/src/assets/app.html +14 -0
  9. package/src/belteResolverPlugin.ts +858 -0
  10. package/src/build.ts +147 -0
  11. package/src/buildCli.ts +129 -0
  12. package/src/buildDisconnected.ts +122 -0
  13. package/src/bundleApp.ts +149 -0
  14. package/src/bundleDisconnectedEntry.ts +17 -0
  15. package/src/cliEntry.ts +25 -0
  16. package/src/clientBuildPlugins.ts +41 -0
  17. package/src/clientEntry.ts +7 -0
  18. package/src/compile.ts +64 -0
  19. package/src/controlServerWorker.ts +422 -0
  20. package/src/dedupeSveltePlugin.ts +66 -0
  21. package/src/devEntry.ts +169 -0
  22. package/src/discoveryEntry.ts +81 -0
  23. package/src/lib/browser/applyStreamedResolution.ts +33 -0
  24. package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
  25. package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
  26. package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
  27. package/src/lib/browser/openResolveStream.ts +42 -0
  28. package/src/lib/browser/page.svelte.ts +258 -0
  29. package/src/lib/browser/pageStreamController.ts +17 -0
  30. package/src/lib/browser/refetchPlaceholder.ts +12 -0
  31. package/src/lib/browser/remoteProxy.ts +37 -0
  32. package/src/lib/browser/socketChannel.ts +192 -0
  33. package/src/lib/browser/socketProxy.ts +57 -0
  34. package/src/lib/browser/startClient.ts +153 -0
  35. package/src/lib/browser/subscribe.ts +131 -0
  36. package/src/lib/browser/types/Errors.ts +9 -0
  37. package/src/lib/browser/types/Layouts.ts +7 -0
  38. package/src/lib/browser/types/Pages.ts +7 -0
  39. package/src/lib/browser/types/StreamingDeferred.ts +9 -0
  40. package/src/lib/bundle/BundleMenu.ts +11 -0
  41. package/src/lib/bundle/BundleMenuItem.ts +24 -0
  42. package/src/lib/bundle/BundleWindow.ts +36 -0
  43. package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
  44. package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
  45. package/src/lib/bundle/bindConnectedFlag.ts +29 -0
  46. package/src/lib/bundle/bindRequestNavigate.ts +31 -0
  47. package/src/lib/bundle/buildWebviewLib.ts +111 -0
  48. package/src/lib/bundle/disconnected.css +9 -0
  49. package/src/lib/bundle/disconnected.svelte +386 -0
  50. package/src/lib/bundle/ensureWebviewLib.ts +20 -0
  51. package/src/lib/bundle/exitWithParent.ts +28 -0
  52. package/src/lib/bundle/infoPlist.ts +46 -0
  53. package/src/lib/bundle/installDownloads.ts +24 -0
  54. package/src/lib/bundle/installMacMenu.ts +39 -0
  55. package/src/lib/bundle/listenLocalControlServer.ts +19 -0
  56. package/src/lib/bundle/native/belteMenu.mm +422 -0
  57. package/src/lib/bundle/native/webview.h +4557 -0
  58. package/src/lib/bundle/onMenu.ts +41 -0
  59. package/src/lib/bundle/openWebview.ts +104 -0
  60. package/src/lib/bundle/pngToIcns.ts +47 -0
  61. package/src/lib/bundle/probeBelteServer.ts +34 -0
  62. package/src/lib/bundle/resolveServerBinary.ts +12 -0
  63. package/src/lib/bundle/resolveWebviewLib.ts +53 -0
  64. package/src/lib/bundle/serverBinaryFilename.ts +8 -0
  65. package/src/lib/bundle/signMacApp.ts +35 -0
  66. package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
  67. package/src/lib/bundle/stableLocalPort.ts +19 -0
  68. package/src/lib/bundle/waitForServer.ts +23 -0
  69. package/src/lib/bundle/webviewCachePath.ts +23 -0
  70. package/src/lib/bundle/webviewLibName.ts +11 -0
  71. package/src/lib/cli/connectToServer.ts +23 -0
  72. package/src/lib/cli/createClient.ts +170 -0
  73. package/src/lib/cli/dispatchCommand.ts +71 -0
  74. package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
  75. package/src/lib/cli/parseArgvForRpc.ts +97 -0
  76. package/src/lib/cli/printHelp.ts +119 -0
  77. package/src/lib/cli/printSessionHelp.ts +27 -0
  78. package/src/lib/cli/printSessionStatus.ts +21 -0
  79. package/src/lib/cli/printTrimmed.ts +8 -0
  80. package/src/lib/cli/printValue.ts +10 -0
  81. package/src/lib/cli/resolveCliTarget.ts +48 -0
  82. package/src/lib/cli/runCli.ts +139 -0
  83. package/src/lib/cli/runSession.ts +105 -0
  84. package/src/lib/cli/startLocalInstance.ts +14 -0
  85. package/src/lib/cli/tokenizeLine.ts +51 -0
  86. package/src/lib/cli/types/CliManifest.ts +9 -0
  87. package/src/lib/cli/types/CliManifestEntry.ts +17 -0
  88. package/src/lib/cli/types/CliTarget.ts +13 -0
  89. package/src/lib/mcp/annotationsForMethod.ts +29 -0
  90. package/src/lib/mcp/createMcpResourceServer.ts +101 -0
  91. package/src/lib/mcp/createMcpServer.ts +42 -0
  92. package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
  93. package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
  94. package/src/lib/mcp/mcpSurface.ts +265 -0
  95. package/src/lib/mcp/toolResultFromResponse.ts +66 -0
  96. package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
  97. package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
  98. package/src/lib/mcp/types/McpResourceContents.ts +10 -0
  99. package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
  100. package/src/lib/mcp/types/McpResourceServer.ts +12 -0
  101. package/src/lib/mcp/types/McpServer.ts +9 -0
  102. package/src/lib/mcp/types/McpServerOptions.ts +16 -0
  103. package/src/lib/server/AppModule.ts +33 -0
  104. package/src/lib/server/DELETE.ts +9 -0
  105. package/src/lib/server/GET.ts +9 -0
  106. package/src/lib/server/HEAD.ts +9 -0
  107. package/src/lib/server/PATCH.ts +9 -0
  108. package/src/lib/server/POST.ts +9 -0
  109. package/src/lib/server/PUT.ts +9 -0
  110. package/src/lib/server/agent.ts +76 -0
  111. package/src/lib/server/appDataDir.ts +15 -0
  112. package/src/lib/server/cli/buildEnvContent.ts +19 -0
  113. package/src/lib/server/cli/createTarGz.ts +76 -0
  114. package/src/lib/server/cli/handleCliDownload.ts +153 -0
  115. package/src/lib/server/cli/handleCliInstall.ts +37 -0
  116. package/src/lib/server/cli/installScript.ts +29 -0
  117. package/src/lib/server/cli/maxSourceMtime.ts +26 -0
  118. package/src/lib/server/cookies.ts +29 -0
  119. package/src/lib/server/env.ts +50 -0
  120. package/src/lib/server/error.ts +70 -0
  121. package/src/lib/server/json.ts +28 -0
  122. package/src/lib/server/jsonl.ts +46 -0
  123. package/src/lib/server/prompts/definePrompt.ts +20 -0
  124. package/src/lib/server/prompts/promptRegistry.ts +9 -0
  125. package/src/lib/server/prompts/registerPrompt.ts +6 -0
  126. package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
  127. package/src/lib/server/prompts/types/Prompt.ts +13 -0
  128. package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
  129. package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
  130. package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
  131. package/src/lib/server/redirect.ts +42 -0
  132. package/src/lib/server/request.ts +18 -0
  133. package/src/lib/server/rpc/defineVerb.ts +133 -0
  134. package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
  135. package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
  136. package/src/lib/server/rpc/parseArgs.ts +95 -0
  137. package/src/lib/server/rpc/registerVerb.ts +6 -0
  138. package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
  139. package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
  140. package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
  141. package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
  142. package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
  143. package/src/lib/server/rpc/unprocessed.ts +14 -0
  144. package/src/lib/server/rpc/verbRegistry.ts +11 -0
  145. package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
  146. package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
  147. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
  148. package/src/lib/server/runtime/acceptsZstd.ts +8 -0
  149. package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
  150. package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
  151. package/src/lib/server/runtime/containsTraversal.ts +37 -0
  152. package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
  153. package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
  154. package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
  155. package/src/lib/server/runtime/createServer.ts +692 -0
  156. package/src/lib/server/runtime/devReloadResponse.ts +35 -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 +35 -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/inProcessServer.ts +20 -0
  163. package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
  164. package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
  165. package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
  166. package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
  167. package/src/lib/server/runtime/mimeForExtension.ts +20 -0
  168. package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
  169. package/src/lib/server/runtime/parsePort.ts +11 -0
  170. package/src/lib/server/runtime/registryManifests.ts +66 -0
  171. package/src/lib/server/runtime/requestContext.ts +5 -0
  172. package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
  173. package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
  174. package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
  175. package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
  176. package/src/lib/server/runtime/serverSlot.ts +13 -0
  177. package/src/lib/server/runtime/setActiveServer.ts +6 -0
  178. package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
  179. package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
  180. package/src/lib/server/runtime/streamFromIterator.ts +86 -0
  181. package/src/lib/server/runtime/streamStash.ts +64 -0
  182. package/src/lib/server/runtime/types/Assets.ts +1 -0
  183. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  184. package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
  185. package/src/lib/server/server.ts +32 -0
  186. package/src/lib/server/socket.ts +31 -0
  187. package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
  188. package/src/lib/server/sockets/defineSocket.ts +167 -0
  189. package/src/lib/server/sockets/lookupSocket.ts +6 -0
  190. package/src/lib/server/sockets/recentHistory.ts +11 -0
  191. package/src/lib/server/sockets/registerSocket.ts +6 -0
  192. package/src/lib/server/sockets/socketOperations.ts +35 -0
  193. package/src/lib/server/sockets/socketRegistry.ts +9 -0
  194. package/src/lib/server/sockets/types/Socket.ts +21 -0
  195. package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
  196. package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
  197. package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
  198. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
  199. package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
  200. package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
  201. package/src/lib/server/sse.ts +53 -0
  202. package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
  203. package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
  204. package/src/lib/shared/HttpError.ts +19 -0
  205. package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
  206. package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
  207. package/src/lib/shared/activeCacheStore.ts +20 -0
  208. package/src/lib/shared/appDataDir.ts +34 -0
  209. package/src/lib/shared/belteImportName.ts +44 -0
  210. package/src/lib/shared/browserClientFlags.ts +10 -0
  211. package/src/lib/shared/buildRpcRequest.ts +70 -0
  212. package/src/lib/shared/bundleLayout.ts +36 -0
  213. package/src/lib/shared/bundled.ts +34 -0
  214. package/src/lib/shared/cache.ts +559 -0
  215. package/src/lib/shared/cacheStoreSlot.ts +16 -0
  216. package/src/lib/shared/canonicalJson.ts +63 -0
  217. package/src/lib/shared/carriesBodyArgs.ts +13 -0
  218. package/src/lib/shared/clearLastConnection.ts +7 -0
  219. package/src/lib/shared/commandNameForUrl.ts +17 -0
  220. package/src/lib/shared/createCacheStore.ts +75 -0
  221. package/src/lib/shared/createPushIterator.ts +93 -0
  222. package/src/lib/shared/createRemoteFunction.ts +99 -0
  223. package/src/lib/shared/decodeResponse.ts +47 -0
  224. package/src/lib/shared/detectTarget.ts +27 -0
  225. package/src/lib/shared/exeSuffix.ts +9 -0
  226. package/src/lib/shared/exitOnBuildFailure.ts +17 -0
  227. package/src/lib/shared/extraForwardHeaders.ts +16 -0
  228. package/src/lib/shared/fileStem.ts +9 -0
  229. package/src/lib/shared/findExportCallSite.ts +479 -0
  230. package/src/lib/shared/forwardHeaders.ts +41 -0
  231. package/src/lib/shared/getRemoteMeta.ts +5 -0
  232. package/src/lib/shared/globalCacheStore.ts +15 -0
  233. package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
  234. package/src/lib/shared/importNamesToStrip.ts +13 -0
  235. package/src/lib/shared/invalidateEvent.ts +11 -0
  236. package/src/lib/shared/isCompileTarget.ts +15 -0
  237. package/src/lib/shared/isDebugEnabled.ts +23 -0
  238. package/src/lib/shared/isModuleNotFound.ts +16 -0
  239. package/src/lib/shared/isReadOnlyMethod.ts +14 -0
  240. package/src/lib/shared/isStreamingResponse.ts +11 -0
  241. package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
  242. package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
  243. package/src/lib/shared/jsonlErrorFrame.ts +24 -0
  244. package/src/lib/shared/keyForRemoteCall.ts +29 -0
  245. package/src/lib/shared/lastConnectionPath.ts +7 -0
  246. package/src/lib/shared/loadEnvFile.ts +17 -0
  247. package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
  248. package/src/lib/shared/loadSvelteConfig.ts +18 -0
  249. package/src/lib/shared/log.ts +104 -0
  250. package/src/lib/shared/manifestModule.ts +39 -0
  251. package/src/lib/shared/memoizeByKey.ts +24 -0
  252. package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
  253. package/src/lib/shared/normalizeTarget.ts +10 -0
  254. package/src/lib/shared/pageUrlForFile.ts +14 -0
  255. package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
  256. package/src/lib/shared/parseEnv.ts +30 -0
  257. package/src/lib/shared/parsePromptMarkdown.ts +34 -0
  258. package/src/lib/shared/parseRouteSegments.ts +22 -0
  259. package/src/lib/shared/prepareRpcModule.ts +59 -0
  260. package/src/lib/shared/prepareSocketModule.ts +49 -0
  261. package/src/lib/shared/programNameForPackage.ts +14 -0
  262. package/src/lib/shared/promptNameForFile.ts +10 -0
  263. package/src/lib/shared/queryStringFromArgs.ts +27 -0
  264. package/src/lib/shared/readEnvFile.ts +15 -0
  265. package/src/lib/shared/readLastConnection.ts +18 -0
  266. package/src/lib/shared/readPackageJson.ts +9 -0
  267. package/src/lib/shared/recordRemoteMeta.ts +5 -0
  268. package/src/lib/shared/remoteMetaStore.ts +16 -0
  269. package/src/lib/shared/resolveClientFlags.ts +20 -0
  270. package/src/lib/shared/responseErrorText.ts +9 -0
  271. package/src/lib/shared/rpcUrlForFile.ts +19 -0
  272. package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
  273. package/src/lib/shared/serializeEnv.ts +18 -0
  274. package/src/lib/shared/setCacheStoreResolver.ts +6 -0
  275. package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
  276. package/src/lib/shared/socketNameForFile.ts +11 -0
  277. package/src/lib/shared/sseErrorFrame.ts +29 -0
  278. package/src/lib/shared/streamResponse.ts +169 -0
  279. package/src/lib/shared/stripImport.ts +27 -0
  280. package/src/lib/shared/subscribableFromResponse.ts +51 -0
  281. package/src/lib/shared/toBunRoutePattern.ts +28 -0
  282. package/src/lib/shared/types/CacheEntry.ts +63 -0
  283. package/src/lib/shared/types/CacheInvalidation.ts +9 -0
  284. package/src/lib/shared/types/CacheOptions.ts +33 -0
  285. package/src/lib/shared/types/CacheSnapshot.ts +16 -0
  286. package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
  287. package/src/lib/shared/types/CacheStore.ts +32 -0
  288. package/src/lib/shared/types/ClientFlags.ts +11 -0
  289. package/src/lib/shared/types/CompileTarget.ts +6 -0
  290. package/src/lib/shared/types/HttpVerb.ts +1 -0
  291. package/src/lib/shared/types/LastConnection.ts +9 -0
  292. package/src/lib/shared/types/PromptArgument.ts +12 -0
  293. package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
  294. package/src/lib/shared/types/RemoteFunction.ts +42 -0
  295. package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
  296. package/src/lib/shared/types/StreamedResolution.ts +10 -0
  297. package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
  298. package/src/lib/shared/types/Subscribable.ts +15 -0
  299. package/src/lib/shared/types/SvelteConfig.ts +5 -0
  300. package/src/lib/shared/withJsonSchema.ts +20 -0
  301. package/src/lib/shared/writeLastConnection.ts +13 -0
  302. package/src/lib/shared/writeRoutesDts.ts +67 -0
  303. package/src/lib/test/clearVerbRegistry.ts +11 -0
  304. package/src/lib/test/createTestClient.ts +78 -0
  305. package/src/preload.ts +20 -0
  306. package/src/scaffold.ts +92 -0
  307. package/src/serverBuildPlugins.ts +25 -0
  308. package/src/serverEntry.ts +94 -0
  309. package/src/sveltePlugin.ts +58 -0
  310. package/src/tailwindStylePreprocessor.ts +62 -0
  311. package/template/bunfig.toml +4 -0
  312. package/template/package.json +19 -0
  313. package/template/src/app.ts +23 -0
  314. package/template/src/browser/app.css +21 -0
  315. package/template/src/browser/app.html +24 -0
  316. package/template/src/browser/pages/about/page.svelte +5 -0
  317. package/template/src/browser/pages/layout.svelte +26 -0
  318. package/template/src/browser/pages/page.svelte +20 -0
  319. package/template/src/bundle/icon.png +0 -0
  320. package/template/src/cli/banner.txt +3 -0
  321. package/template/src/cli/footer.txt +1 -0
  322. package/template/src/server/config.ts +17 -0
  323. package/template/src/server/rpc/getHello.ts +35 -0
  324. package/template/svelte.config.js +12 -0
  325. package/template/tsconfig.json +18 -0
  326. package/tsconfig.app.json +16 -0
package/src/build.ts ADDED
@@ -0,0 +1,147 @@
1
+ import { clientBuildPlugins } from './clientBuildPlugins.ts'
2
+ import { exitOnBuildFailure } from './lib/shared/exitOnBuildFailure.ts'
3
+ import { loadSvelteConfig } from './lib/shared/loadSvelteConfig.ts'
4
+ import { log } from './lib/shared/log.ts'
5
+ import type { SvelteConfig } from './lib/shared/types/SvelteConfig.ts'
6
+
7
+ const CLIENT_ENTRY = new URL('./clientEntry.ts', import.meta.url).pathname
8
+
9
+ /*
10
+ Builds the client-side bundle into `${cwd}/dist/_app`. Runs Bun.build with
11
+ the svelte-dedupe plugin, the svelte loader, the virtual-module resolver,
12
+ and (optionally) Tailwind. When `compress`, each emitted file is also
13
+ written as a zstd-compressed `.zst` sibling (level 22 — paid once at build
14
+ time) so the server can stream the precompressed bytes directly when the
15
+ client supports it, and decompress on the fly for older clients. Dev skips
16
+ compression (zstd-22 on every rebuild dwarfs the bundle itself) — the
17
+ server falls back to serving the plain bytes when no `.zst` sibling exists.
18
+
19
+ The bundle is emitted into a per-build staging dir, then swapped into
20
+ `_app` with two atomic renames. This keeps every build's writes isolated to
21
+ a unique path (so a stray concurrent build can never `rm` files Bun is
22
+ mid-flushing — the "writing sourcemap: No such file or directory" race) and
23
+ means a long-running dev server reading `_app` lazily off disk never sees a
24
+ half-built or emptied directory.
25
+
26
+ `clean` (one-shot builds) clears the whole dist up front so downstream
27
+ writers — the bundle's connect screen, the CLI manifest — start fresh; the
28
+ dev orchestrator passes `clean: false` to leave the live dist untouched and
29
+ only swap `_app` at the end.
30
+
31
+ Returns whether the build succeeded. Never throws: a thrown Bun.build / fs
32
+ error is logged and treated as a failed build, so the dev loop (and its
33
+ last-good server) survives instead of crashing and orphaning the child. By
34
+ default a failure exits the process (one-shot `belte build` / `compile`);
35
+ the dev orchestrator passes `exitOnFailure: false`.
36
+ */
37
+ export async function build({
38
+ cwd = process.cwd(),
39
+ svelteConfig,
40
+ minify = true,
41
+ compress = true,
42
+ clean = true,
43
+ exitOnFailure = true,
44
+ }: {
45
+ cwd?: string
46
+ svelteConfig?: SvelteConfig
47
+ minify?: boolean
48
+ compress?: boolean
49
+ clean?: boolean
50
+ exitOnFailure?: boolean
51
+ } = {}): Promise<boolean> {
52
+ const distDir = `${cwd}/dist`
53
+ const outDir = `${distDir}/_app`
54
+ // Per-build staging + holding dirs; the suffix isolates concurrent builds.
55
+ const buildId = crypto.randomUUID().slice(0, 8)
56
+ const stagingDir = `${distDir}/_app.staging-${buildId}`
57
+ const previousDir = `${distDir}/_app.old-${buildId}`
58
+
59
+ const fail = (): boolean => {
60
+ if (exitOnFailure) {
61
+ process.exit(1)
62
+ }
63
+ return false
64
+ }
65
+
66
+ try {
67
+ // shell-rm/-mv are the impure boundary for the dist swap — Bun.$ is first-party.
68
+ if (clean) {
69
+ await Bun.$`rm -rf ${distDir}`.quiet()
70
+ }
71
+
72
+ const config = svelteConfig ?? (await loadSvelteConfig(cwd))
73
+ const plugins = await clientBuildPlugins({
74
+ cwd,
75
+ svelteConfig: config,
76
+ tailwindWarning: 'bun-plugin-tailwind not installed; building without Tailwind',
77
+ })
78
+
79
+ const result = await Bun.build({
80
+ entrypoints: [CLIENT_ENTRY],
81
+ outdir: stagingDir,
82
+ target: 'browser',
83
+ splitting: true,
84
+ minify,
85
+ sourcemap: 'linked',
86
+ naming: {
87
+ entry: 'client-[hash].[ext]',
88
+ chunk: '[name]-[hash].[ext]',
89
+ asset: '[name].[ext]',
90
+ },
91
+ plugins,
92
+ })
93
+
94
+ if (!result.success) {
95
+ await Bun.$`rm -rf ${stagingDir}`.quiet().nothrow()
96
+ if (exitOnFailure) {
97
+ exitOnBuildFailure(result)
98
+ }
99
+ result.logs.forEach((entry) => {
100
+ log.error(entry)
101
+ })
102
+ return false
103
+ }
104
+
105
+ // Dev skips the zstd siblings (paths still point into stagingDir here).
106
+ const compressedBytes = compress
107
+ ? (
108
+ await Promise.all(
109
+ result.outputs.map(async (output) => {
110
+ const bytes = await Bun.file(output.path).bytes()
111
+ const compressed = await Bun.zstdCompress(bytes, { level: 22 })
112
+ await Bun.write(`${output.path}.zst`, compressed)
113
+ return compressed.byteLength
114
+ }),
115
+ )
116
+ ).reduce((total, length) => total + length, 0)
117
+ : 0
118
+
119
+ /*
120
+ Swap staging into _app with two renames: move any existing _app aside,
121
+ then rename staging into place. The window where _app is absent is a
122
+ single rename, so a reader (the running dev server) never observes a
123
+ partial bundle. nothrow on the first move: no _app exists on the first
124
+ build or after `clean`.
125
+ */
126
+ await Bun.$`mv ${outDir} ${previousDir}`.quiet().nothrow()
127
+ await Bun.$`mv ${stagingDir} ${outDir}`.quiet()
128
+ await Bun.$`rm -rf ${previousDir}`.quiet().nothrow()
129
+
130
+ if (compress) {
131
+ log.info(
132
+ `wrote ${result.outputs.length} files to ${outDir} (+${result.outputs.length} .zst, ${(compressedBytes / 1024).toFixed(1)} KiB total)`,
133
+ )
134
+ // Per-file paths are noise at startup; surface them only under DEBUG=belte:build.
135
+ result.outputs.forEach((output) => {
136
+ log.debug('belte:build', ` - ${output.path.replace(stagingDir, outDir)}`)
137
+ })
138
+ } else {
139
+ log.info(`wrote ${result.outputs.length} files to ${outDir}`)
140
+ }
141
+ return true
142
+ } catch (error) {
143
+ log.error(error)
144
+ await Bun.$`rm -rf ${stagingDir} ${previousDir}`.quiet().nothrow()
145
+ return fail()
146
+ }
147
+ }
@@ -0,0 +1,129 @@
1
+ import { dirname, join } from 'node:path'
2
+ import { build } from './build.ts'
3
+ import { compile } from './compile.ts'
4
+ import { detectTarget } from './lib/shared/detectTarget.ts'
5
+ import { exeSuffix } from './lib/shared/exeSuffix.ts'
6
+ import { exitOnBuildFailure } from './lib/shared/exitOnBuildFailure.ts'
7
+ import { loadSvelteConfig } from './lib/shared/loadSvelteConfig.ts'
8
+ import { log } from './lib/shared/log.ts'
9
+ import { programNameForPackage } from './lib/shared/programNameForPackage.ts'
10
+ import { readPackageJson } from './lib/shared/readPackageJson.ts'
11
+ import type { CompileTarget } from './lib/shared/types/CompileTarget.ts'
12
+ import { serverBuildPlugins } from './serverBuildPlugins.ts'
13
+
14
+ const DISCOVERY_ENTRY = new URL('./discoveryEntry.ts', import.meta.url).pathname
15
+ const CLI_ENTRY = new URL('./cliEntry.ts', import.meta.url).pathname
16
+
17
+ /*
18
+ CLI binary build. The CLI is a thin remote client — it bakes in the per-rpc
19
+ manifest and talks to a running server over HTTP — but the **full** binary ships
20
+ the compiled server beside it, so `/start` can spawn a local instance.
21
+
22
+ 1. Client build (once): `build` produces the platform-independent `dist/_app`
23
+ the server binaries embed. It clears dist first, so it runs before everything.
24
+ 2. Discovery: build the discovery entry into a temporary JS bundle and run it. It
25
+ imports every rpc/socket module so defineVerb / defineSocket populate the
26
+ registries, then prints the CLI manifest to stdout → `dist/cli-manifest.json`.
27
+ 3. Per target: a server binary (`compile`, reusing the shared `dist/_app` via
28
+ `buildClient:false`) plus the CLI binary, written side by side. The resolver's
29
+ `belte:cli-manifest` virtual splices in the manifest from step 2.
30
+
31
+ `platforms` cross-compiles per target into `dist/cli-thin/<platform>/` (the layout
32
+ the /__belte/cli download endpoint serves): `<programName>` + `server` together.
33
+ */
34
+ export async function buildCli({
35
+ cwd = process.cwd(),
36
+ target = detectTarget(),
37
+ outfile,
38
+ platforms,
39
+ }: {
40
+ cwd?: string
41
+ target?: CompileTarget
42
+ outfile?: string
43
+ platforms?: CompileTarget[]
44
+ } = {}): Promise<string[]> {
45
+ const distDir = `${cwd}/dist`
46
+ const manifestPath = `${distDir}/cli-manifest.json`
47
+ const discoveryOut = `${distDir}/_discovery.js`
48
+
49
+ const svelteConfig = await loadSvelteConfig(cwd)
50
+ const plugins = serverBuildPlugins({ cwd, svelteConfig })
51
+
52
+ // Step 1 — client build once (clears dist, writes _app). Every server binary
53
+ // embeds it, so it must precede discovery and the per-target compiles.
54
+ await build({ cwd, svelteConfig })
55
+
56
+ /*
57
+ Step 2 — discovery. Build a runnable bundle, execute it under bun, capture
58
+ stdout. We don't `bun build --compile` here because the discovery output is
59
+ throwaway; a plain JS bundle runs faster. Additive — does not clear dist.
60
+ */
61
+ const discoveryResult = await Bun.build({
62
+ entrypoints: [DISCOVERY_ENTRY],
63
+ target: 'bun',
64
+ outdir: distDir,
65
+ naming: '_discovery.js',
66
+ plugins,
67
+ })
68
+ exitOnBuildFailure(discoveryResult)
69
+
70
+ const proc = Bun.spawn({
71
+ cmd: ['bun', discoveryOut],
72
+ cwd,
73
+ stdio: ['ignore', 'pipe', 'pipe'],
74
+ })
75
+ const [stdout, stderr, exitCode] = await Promise.all([
76
+ new Response(proc.stdout).text(),
77
+ new Response(proc.stderr).text(),
78
+ proc.exited,
79
+ ])
80
+ if (exitCode !== 0) {
81
+ log.error(`discovery exited ${exitCode}:\n${stderr}`)
82
+ process.exit(1)
83
+ }
84
+ await Bun.write(manifestPath, stdout)
85
+ await Bun.$`rm -f ${discoveryOut}`.quiet()
86
+ const entryCount = Object.keys(JSON.parse(stdout) as Record<string, unknown>).length
87
+ log.info(`discovered ${entryCount} cli commands → ${manifestPath}`)
88
+
89
+ const programName = await readProgramName(cwd)
90
+
91
+ /*
92
+ Step 3 — compile a CLI binary + sibling server binary for a single target,
93
+ written side by side so `resolveServerBinary()` finds the server next to the
94
+ running CLI. The server reuses the shared client build (buildClient:false).
95
+ */
96
+ async function buildTargetPair(platformTarget: CompileTarget, cliOut: string): Promise<string> {
97
+ const serverOut = join(dirname(cliOut), `server${exeSuffix(platformTarget)}`)
98
+ await compile({ cwd, target: platformTarget, outfile: serverOut, buildClient: false })
99
+ const result = await Bun.build({
100
+ entrypoints: [CLI_ENTRY],
101
+ target: 'bun',
102
+ compile: { target: platformTarget, outfile: cliOut },
103
+ plugins,
104
+ })
105
+ exitOnBuildFailure(result)
106
+ log.success(`compiled cli + server: ${cliOut}`)
107
+ return cliOut
108
+ }
109
+
110
+ if (platforms && platforms.length > 0) {
111
+ // Cross-compile every target in parallel — each pair is independent.
112
+ return Promise.all(
113
+ platforms.map(async (platformTarget) => {
114
+ const shortName = platformTarget.replace(/^bun-/, '')
115
+ const cliOut = `${distDir}/cli-thin/${shortName}/${programName}${exeSuffix(platformTarget)}`
116
+ await Bun.$`mkdir -p ${`${distDir}/cli-thin/${shortName}`}`.quiet()
117
+ return buildTargetPair(platformTarget, cliOut)
118
+ }),
119
+ )
120
+ }
121
+
122
+ const cliOut = outfile ?? `${distDir}/cli${exeSuffix(target)}`
123
+ return [await buildTargetPair(target, cliOut)]
124
+ }
125
+
126
+ async function readProgramName(cwd: string): Promise<string> {
127
+ const pkg = (await readPackageJson(cwd)) as { name?: string } | undefined
128
+ return programNameForPackage(pkg?.name)
129
+ }
@@ -0,0 +1,122 @@
1
+ import { clientBuildPlugins } from './clientBuildPlugins.ts'
2
+ import { exitOnBuildFailure } from './lib/shared/exitOnBuildFailure.ts'
3
+ import { log } from './lib/shared/log.ts'
4
+ import type { SvelteConfig } from './lib/shared/types/SvelteConfig.ts'
5
+
6
+ const ENTRY = new URL('./bundleDisconnectedEntry.ts', import.meta.url).pathname
7
+ const CSS_ENTRY = new URL('./lib/bundle/disconnected.css', import.meta.url).pathname
8
+
9
+ /*
10
+ Default screen logo: a minimal inline-SVG belte mark, used when the project ships
11
+ no src/bundle/logo.png. Inline SVG keeps the bundle self-contained with no asset
12
+ file to vendor; a project overrides it just by adding the PNG.
13
+ */
14
+ const DEFAULT_LOGO = `data:image/svg+xml,${encodeURIComponent(
15
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">' +
16
+ '<rect width="64" height="64" rx="16" fill="#111827"/>' +
17
+ '<text x="32" y="45" font-family="system-ui,sans-serif" font-size="38" ' +
18
+ 'font-weight="700" fill="#fff" text-anchor="middle">b</text></svg>',
19
+ )}`
20
+
21
+ /*
22
+ Builds the bundle connect screen into a single self-contained HTML string and
23
+ writes it to `dist/bundle-disconnected.html`, which the launcher bakes in via the
24
+ `belte:bundle-disconnected` virtual. The client bundle (Svelte component +
25
+ injected CSS + compiled Tailwind) is inlined into the page so the launcher serves
26
+ it with zero external requests; the logo is embedded as a data URI. The app title
27
+ (`window.__BELTE_TITLE__`) is injected by the launcher at serve time, so a
28
+ `<!--belte:connect-config-->` marker is left in <head> for it.
29
+
30
+ Must run after the client build has cleared and repopulated dist (it writes a
31
+ file into dist), and before the launcher build that reads it. Uses no outdir of
32
+ its own — Bun.build artifacts are read from memory — so it never touches dist
33
+ beyond the one file it writes.
34
+ */
35
+ export async function buildDisconnected({
36
+ cwd = process.cwd(),
37
+ svelteConfig,
38
+ }: {
39
+ cwd?: string
40
+ svelteConfig?: SvelteConfig
41
+ } = {}): Promise<string> {
42
+ const plugins = await clientBuildPlugins({
43
+ cwd,
44
+ svelteConfig,
45
+ tailwindWarning:
46
+ 'bun-plugin-tailwind not installed; building connect screen without Tailwind',
47
+ })
48
+
49
+ /*
50
+ The Tailwind CSS rides as its own entrypoint rather than a `.css` import from
51
+ the entry (which TS can't type), so it compiles to a standalone artifact we
52
+ inline alongside the JS — the component carries no <style> of its own.
53
+ */
54
+ const result = await Bun.build({
55
+ entrypoints: [ENTRY, CSS_ENTRY],
56
+ target: 'browser',
57
+ minify: true,
58
+ plugins,
59
+ })
60
+ exitOnBuildFailure(result)
61
+
62
+ // Collect the JS bundle + extracted CSS from the in-memory artifacts.
63
+ let js = ''
64
+ let css = ''
65
+ for (const output of result.outputs) {
66
+ const text = await output.text()
67
+ if (output.path.endsWith('.css')) {
68
+ css += text
69
+ } else if (output.path.endsWith('.js')) {
70
+ js += text
71
+ }
72
+ }
73
+
74
+ const logo = await readLogo(cwd)
75
+ const html = composeHtml({ js, css, logo })
76
+ const outPath = `${cwd}/dist/bundle-disconnected.html`
77
+ await Bun.write(outPath, html)
78
+ log.success(`built connect screen: ${outPath} (${(html.length / 1024).toFixed(1)} KiB)`)
79
+ return outPath
80
+ }
81
+
82
+ // Reads the project's src/bundle/logo.png as a data URI, or the default mark.
83
+ async function readLogo(cwd: string): Promise<string> {
84
+ const userLogo = Bun.file(`${cwd}/src/bundle/logo.png`)
85
+ if (await userLogo.exists()) {
86
+ const bytes = await userLogo.bytes()
87
+ return `data:image/png;base64,${bytes.toBase64()}`
88
+ }
89
+ return DEFAULT_LOGO
90
+ }
91
+
92
+ /*
93
+ Escapes a closing `</script>` so an inline `<script>` body can't be terminated
94
+ early by content in the bundle (rare, but a `</script>` substring would break the
95
+ page). The browser still parses `<\/script>` as the intended characters.
96
+ */
97
+ function escapeScriptBody(value: string): string {
98
+ return value.replace(/<\/(script)/gi, '<\\/$1')
99
+ }
100
+
101
+ // Assembles the final standalone HTML document from the inlined pieces.
102
+ function composeHtml({ js, css, logo }: { js: string; css: string; logo: string }): string {
103
+ const logoScript = `window.__BELTE_LOGO__=${JSON.stringify(logo)}`
104
+ return `<!doctype html>
105
+ <html lang="en">
106
+ <head>
107
+ <meta charset="UTF-8" />
108
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
109
+ <!--belte:connect-config-->
110
+ <script>${escapeScriptBody(logoScript)}</script>
111
+ <!-- Paint the screen background before the client mounts, so there's no white
112
+ flash ahead of the splash (gray-50 / gray-950 to match the rendered screen). -->
113
+ <style>html,body{margin:0;background:#f9fafb}@media (prefers-color-scheme:dark){html,body{background:#030712}}</style>
114
+ <style>${css}</style>
115
+ </head>
116
+ <body>
117
+ <div id="app"></div>
118
+ <script type="module">${escapeScriptBody(js)}</script>
119
+ </body>
120
+ </html>
121
+ `
122
+ }
@@ -0,0 +1,149 @@
1
+ import { dirname } from 'node:path'
2
+ import { buildDisconnected } from './buildDisconnected.ts'
3
+ import { compile } from './compile.ts'
4
+ import { ensureWebviewLib } from './lib/bundle/ensureWebviewLib.ts'
5
+ import { infoPlist } from './lib/bundle/infoPlist.ts'
6
+ import { pngToIcns } from './lib/bundle/pngToIcns.ts'
7
+ import { serverBinaryFilename } from './lib/bundle/serverBinaryFilename.ts'
8
+ import { signMacApp } from './lib/bundle/signMacApp.ts'
9
+ import { webviewLibName } from './lib/bundle/webviewLibName.ts'
10
+ import { bundleLayout } from './lib/shared/bundleLayout.ts'
11
+ import { detectTarget } from './lib/shared/detectTarget.ts'
12
+ import { exitOnBuildFailure } from './lib/shared/exitOnBuildFailure.ts'
13
+ import { loadSvelteConfig } from './lib/shared/loadSvelteConfig.ts'
14
+ import { log } from './lib/shared/log.ts'
15
+ import { programNameForPackage } from './lib/shared/programNameForPackage.ts'
16
+ import { readPackageJson } from './lib/shared/readPackageJson.ts'
17
+ import { serverBuildPlugins } from './serverBuildPlugins.ts'
18
+
19
+ const APP_ENTRY = new URL('./appEntry.ts', import.meta.url).pathname
20
+ const WORKER_ENTRY = new URL('./controlServerWorker.ts', import.meta.url).pathname
21
+
22
+ /*
23
+ Assembles a movable, self-contained app bundle for the host platform —
24
+ no cross-compilation, and on macOS an ad-hoc seal so it launches on other
25
+ Macs (signMacApp). Three pieces travel together so the app runs on another
26
+ machine of the same OS with nothing installed:
27
+
28
+ - the standalone server binary (`compile()`, assets embedded)
29
+ - the launcher binary (appEntry — spawns the server, opens the webview)
30
+ - the native webview shared library
31
+
32
+ macOS gets a `.app` bundle (`Contents/MacOS/` + `Contents/Frameworks/` +
33
+ Info.plist); other platforms get a flat `<name>/` directory with the two
34
+ binaries and the lib side by side. The launcher finds both relatives at
35
+ runtime via resolveServerBinary / resolveWebviewLib.
36
+ */
37
+ export async function bundleApp({ cwd = process.cwd() }: { cwd?: string } = {}): Promise<string> {
38
+ const target = detectTarget()
39
+ const { name, version } = await readPackage(cwd)
40
+ const programName = programNameForPackage(name)
41
+ const svelteConfig = await loadSvelteConfig(cwd)
42
+
43
+ /*
44
+ Layout differs by OS: a macOS .app nests binaries under Contents/MacOS, the
45
+ lib under Contents/Frameworks, and data under Contents/Resources; elsewhere
46
+ everything sits flat in one directory. bundleLayout derives the rest from
47
+ binDir — the same source the boot readers resolve from — so build and runtime
48
+ agree on where the lib, resources, and shipped `.env` land.
49
+ */
50
+ const isMac = process.platform === 'darwin'
51
+ const bundleRoot = isMac ? `${cwd}/dist/${programName}.app` : `${cwd}/dist/${programName}`
52
+ const binDir = isMac ? `${bundleRoot}/Contents/MacOS` : bundleRoot
53
+ const { libDir, resourcesDir, envPath } = bundleLayout(binDir)
54
+
55
+ await Bun.$`rm -rf ${bundleRoot}`.quiet()
56
+ await Bun.$`mkdir -p ${binDir} ${libDir}`.quiet()
57
+
58
+ // 1. Server binary — self-contained, embeds the client assets. compile()
59
+ // runs the client build, which clears dist first, so it must precede the
60
+ // connect-screen build that writes into dist.
61
+ await compile({ cwd, target, outfile: `${binDir}/${serverBinaryFilename()}` })
62
+
63
+ /*
64
+ Opt-in: ship the project's `bundle.env` as the shipped `.env`, which the
65
+ server loads at boot (loadEnvFromBinaryDir) as its default config layer. A
66
+ dedicated file, never the working `.env` — a compiled bundle is extractable,
67
+ so only ship-safe defaults belong here; user-specific/secret values come from
68
+ the data-dir `.env` instead. Named outside Bun's `.env.*` autoload family on
69
+ purpose: it's a build input, not a runtime overlay, so `bun dev`/`bun start`
70
+ never pick it up. bundleLayout places it under Contents/Resources
71
+ in a macOS `.app` (sealed as a resource, so it survives codesign) and beside
72
+ the binaries otherwise. Skipped when absent.
73
+ */
74
+ const bundleEnv = Bun.file(`${cwd}/bundle.env`)
75
+ if (await bundleEnv.exists()) {
76
+ await Bun.$`mkdir -p ${dirname(envPath)}`.quiet()
77
+ await Bun.write(envPath, bundleEnv)
78
+ }
79
+
80
+ // 2. Connect screen — bake dist/bundle-disconnected.html before the launcher
81
+ // build, which inlines it via the belte:bundle-disconnected virtual.
82
+ await buildDisconnected({ cwd, svelteConfig })
83
+
84
+ // 3. Launcher binary — named after the program so CFBundleExecutable matches.
85
+ const launcherSuffix = target.includes('windows') ? '.exe' : ''
86
+ const launcherPath = `${binDir}/${programName}${launcherSuffix}`
87
+ const launcherResult = await Bun.build({
88
+ entrypoints: [APP_ENTRY],
89
+ target: 'bun',
90
+ compile: { target, outfile: launcherPath },
91
+ plugins: serverBuildPlugins({ cwd, svelteConfig }),
92
+ /*
93
+ Inject the worker's absolute path as a static literal. `new Worker()` is
94
+ embedded into the standalone binary only when its specifier is a build-time
95
+ literal, and a relative one would resolve against `cwd` (the consumer
96
+ project) rather than appEntry's directory — so the launcher passes the
97
+ absolute path through this define instead.
98
+ */
99
+ define: { __BELTE_WORKER_ENTRY__: JSON.stringify(WORKER_ENTRY) },
100
+ })
101
+ exitOnBuildFailure(launcherResult)
102
+
103
+ // 4. Webview lib — built from the vendored source if needed, then copied
104
+ // beside the binaries (or into Frameworks on macOS) so the bundle is self-contained.
105
+ const libSource = await ensureWebviewLib(cwd)
106
+ await Bun.write(`${libDir}/${webviewLibName()}`, Bun.file(libSource))
107
+
108
+ /*
109
+ macOS-only: produce Contents/Resources/icon.icns from an optional project
110
+ icon, then write the Info.plist that makes the .app launchable from
111
+ Finder, wiring CFBundleIconFile when an icon was produced. A ready-made
112
+ src/bundle/icon.icns is used as-is; otherwise src/bundle/icon.png is
113
+ converted via sips + iconutil so authors don't need to make an .icns.
114
+ */
115
+ if (isMac) {
116
+ const icnsSource = `${cwd}/src/bundle/icon.icns`
117
+ const pngSource = `${cwd}/src/bundle/icon.png`
118
+ let hasIcon = false
119
+ if (await Bun.file(icnsSource).exists()) {
120
+ await Bun.$`mkdir -p ${resourcesDir}`.quiet()
121
+ await Bun.write(`${resourcesDir}/icon.icns`, Bun.file(icnsSource))
122
+ hasIcon = true
123
+ } else if (await Bun.file(pngSource).exists()) {
124
+ await Bun.$`mkdir -p ${resourcesDir}`.quiet()
125
+ hasIcon = await pngToIcns(pngSource, `${resourcesDir}/icon.icns`)
126
+ }
127
+ await Bun.write(
128
+ `${bundleRoot}/Contents/Info.plist`,
129
+ infoPlist({ name: programName, version, icon: hasIcon ? 'icon' : undefined }),
130
+ )
131
+
132
+ // Seal the finished bundle so it launches on other Macs — must run last,
133
+ // after every binary, the lib, and Info.plist are in place.
134
+ await signMacApp(bundleRoot, [
135
+ `${libDir}/${webviewLibName()}`,
136
+ `${binDir}/${serverBinaryFilename()}`,
137
+ launcherPath,
138
+ ])
139
+ }
140
+
141
+ log.success(`bundled app: ${bundleRoot} (target: ${target})`)
142
+ return bundleRoot
143
+ }
144
+
145
+ // Reads name + version from package.json, with fallbacks when absent.
146
+ async function readPackage(cwd: string): Promise<{ name: string | undefined; version: string }> {
147
+ const pkg = (await readPackageJson(cwd)) as { name?: string; version?: string } | undefined
148
+ return { name: pkg?.name, version: pkg?.version ?? '0.0.0' }
149
+ }
@@ -0,0 +1,17 @@
1
+ import { mount } from 'svelte'
2
+ // @ts-expect-error virtual module resolved by belteResolverPlugin
3
+ import Disconnected from './_virtual/bundle-disconnected-component.ts'
4
+
5
+ /*
6
+ Client entry for the bundle connect screen. Standalone — it mounts the
7
+ disconnected component (the user's src/bundle/disconnected.svelte override or the
8
+ lib default, picked by the resolver) into #app, with no router or SSR hydration.
9
+ buildDisconnected bundles this into a single self-contained HTML file (the Tailwind
10
+ CSS is a separate build entrypoint, not imported here). The `svelte` import sits
11
+ first so biome's import sorting leaves the `_virtual` component's `@ts-expect-error`
12
+ attached.
13
+ */
14
+ const target = document.getElementById('app')
15
+ if (target) {
16
+ mount(Disconnected, { target })
17
+ }
@@ -0,0 +1,25 @@
1
+ // @ts-expect-error virtual module resolved by belteResolverPlugin
2
+ import { banner, footer } from './_virtual/cli-chrome.ts'
3
+ // @ts-expect-error virtual module resolved by belteResolverPlugin
4
+ import manifest from './_virtual/cli-manifest.ts'
5
+ // @ts-expect-error virtual module resolved by belteResolverPlugin
6
+ import programName from './_virtual/cli-name.ts'
7
+ import { runCli } from './lib/cli/runCli.ts'
8
+
9
+ /*
10
+ Standalone CLI binary entry. Compiled with `bun build --compile` into
11
+ `dist/cli` (or `dist/cli-thin/<platform>/` for cross-builds). The CLI is
12
+ a thin remote client — no handler code is bundled; it talks to a running
13
+ server over HTTP (BELTE_APP_URL at runtime). The bundler emits:
14
+ - belte:cli-manifest — the per-rpc manifest (method, url, jsonSchema)
15
+ - belte:cli-name — the program name from package.json
16
+ - belte:cli-chrome — optional banner/footer text from src/cli/
17
+ */
18
+ const exitCode = await runCli({
19
+ programName,
20
+ manifest,
21
+ banner,
22
+ footer,
23
+ argv: process.argv.slice(2),
24
+ })
25
+ process.exit(exitCode)
@@ -0,0 +1,41 @@
1
+ import type { BunPlugin } from 'bun'
2
+ import { belteResolverPlugin } from './belteResolverPlugin.ts'
3
+ import { dedupeSveltePlugin } from './dedupeSveltePlugin.ts'
4
+ import { isModuleNotFound } from './lib/shared/isModuleNotFound.ts'
5
+ import { log } from './lib/shared/log.ts'
6
+ import type { SvelteConfig } from './lib/shared/types/SvelteConfig.ts'
7
+ import { sveltePlugin } from './sveltePlugin.ts'
8
+
9
+ /*
10
+ The client-target Bun.build plugin chain shared by the page bundle (build)
11
+ and the bundle connect screen (buildDisconnected): svelte-dedupe, the svelte
12
+ client loader, belte's virtual-module resolver, and the optional Tailwind
13
+ plugin. Tailwind is an optional peer — a genuine "not installed" builds
14
+ without it, but any other load error surfaces (a plugin that loaded then
15
+ threw on a real misconfig must not silently ship unstyled). `tailwindWarning`
16
+ names what each caller builds without when Tailwind is absent.
17
+ */
18
+ export async function clientBuildPlugins({
19
+ cwd,
20
+ svelteConfig,
21
+ tailwindWarning,
22
+ }: {
23
+ cwd: string
24
+ svelteConfig?: SvelteConfig
25
+ tailwindWarning: string
26
+ }): Promise<BunPlugin[]> {
27
+ const plugins: BunPlugin[] = [
28
+ dedupeSveltePlugin({ cwd, conditions: ['browser', 'default'] }),
29
+ sveltePlugin({ generate: 'client', svelteConfig }),
30
+ belteResolverPlugin({ cwd, target: 'client' }),
31
+ ]
32
+ try {
33
+ plugins.push((await import('bun-plugin-tailwind')).default)
34
+ } catch (error) {
35
+ if (!isModuleNotFound(error)) {
36
+ throw error
37
+ }
38
+ log.warn(tailwindWarning)
39
+ }
40
+ return plugins
41
+ }
@@ -0,0 +1,7 @@
1
+ // @ts-expect-error virtual module resolved by belteResolverPlugin
2
+ import { layouts } from './_virtual/layouts.ts'
3
+ // @ts-expect-error virtual module resolved by belteResolverPlugin
4
+ import { pages } from './_virtual/pages.ts'
5
+ import { startClient } from './lib/browser/startClient.ts'
6
+
7
+ await startClient({ pages, layouts })