@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.
- package/CHANGELOG.md +313 -0
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/bin/belte.ts +183 -0
- package/package.json +110 -0
- package/src/App.svelte +31 -0
- package/src/appEntry.ts +151 -0
- package/src/assets/app.html +14 -0
- package/src/belteResolverPlugin.ts +858 -0
- package/src/build.ts +147 -0
- package/src/buildCli.ts +129 -0
- package/src/buildDisconnected.ts +122 -0
- package/src/bundleApp.ts +149 -0
- package/src/bundleDisconnectedEntry.ts +17 -0
- package/src/cliEntry.ts +25 -0
- package/src/clientBuildPlugins.ts +41 -0
- package/src/clientEntry.ts +7 -0
- package/src/compile.ts +64 -0
- package/src/controlServerWorker.ts +422 -0
- package/src/dedupeSveltePlugin.ts +66 -0
- package/src/devEntry.ts +169 -0
- package/src/discoveryEntry.ts +81 -0
- package/src/lib/browser/applyStreamedResolution.ts +33 -0
- package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
- package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
- package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
- package/src/lib/browser/openResolveStream.ts +42 -0
- package/src/lib/browser/page.svelte.ts +258 -0
- package/src/lib/browser/pageStreamController.ts +17 -0
- package/src/lib/browser/refetchPlaceholder.ts +12 -0
- package/src/lib/browser/remoteProxy.ts +37 -0
- package/src/lib/browser/socketChannel.ts +192 -0
- package/src/lib/browser/socketProxy.ts +57 -0
- package/src/lib/browser/startClient.ts +153 -0
- package/src/lib/browser/subscribe.ts +131 -0
- package/src/lib/browser/types/Errors.ts +9 -0
- package/src/lib/browser/types/Layouts.ts +7 -0
- package/src/lib/browser/types/Pages.ts +7 -0
- package/src/lib/browser/types/StreamingDeferred.ts +9 -0
- package/src/lib/bundle/BundleMenu.ts +11 -0
- package/src/lib/bundle/BundleMenuItem.ts +24 -0
- package/src/lib/bundle/BundleWindow.ts +36 -0
- package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
- package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
- package/src/lib/bundle/bindConnectedFlag.ts +29 -0
- package/src/lib/bundle/bindRequestNavigate.ts +31 -0
- package/src/lib/bundle/buildWebviewLib.ts +111 -0
- package/src/lib/bundle/disconnected.css +9 -0
- package/src/lib/bundle/disconnected.svelte +386 -0
- package/src/lib/bundle/ensureWebviewLib.ts +20 -0
- package/src/lib/bundle/exitWithParent.ts +28 -0
- package/src/lib/bundle/infoPlist.ts +46 -0
- package/src/lib/bundle/installDownloads.ts +24 -0
- package/src/lib/bundle/installMacMenu.ts +39 -0
- package/src/lib/bundle/listenLocalControlServer.ts +19 -0
- package/src/lib/bundle/native/belteMenu.mm +422 -0
- package/src/lib/bundle/native/webview.h +4557 -0
- package/src/lib/bundle/onMenu.ts +41 -0
- package/src/lib/bundle/openWebview.ts +104 -0
- package/src/lib/bundle/pngToIcns.ts +47 -0
- package/src/lib/bundle/probeBelteServer.ts +34 -0
- package/src/lib/bundle/resolveServerBinary.ts +12 -0
- package/src/lib/bundle/resolveWebviewLib.ts +53 -0
- package/src/lib/bundle/serverBinaryFilename.ts +8 -0
- package/src/lib/bundle/signMacApp.ts +35 -0
- package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
- package/src/lib/bundle/stableLocalPort.ts +19 -0
- package/src/lib/bundle/waitForServer.ts +23 -0
- package/src/lib/bundle/webviewCachePath.ts +23 -0
- package/src/lib/bundle/webviewLibName.ts +11 -0
- package/src/lib/cli/connectToServer.ts +23 -0
- package/src/lib/cli/createClient.ts +170 -0
- package/src/lib/cli/dispatchCommand.ts +71 -0
- package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
- package/src/lib/cli/parseArgvForRpc.ts +97 -0
- package/src/lib/cli/printHelp.ts +119 -0
- package/src/lib/cli/printSessionHelp.ts +27 -0
- package/src/lib/cli/printSessionStatus.ts +21 -0
- package/src/lib/cli/printTrimmed.ts +8 -0
- package/src/lib/cli/printValue.ts +10 -0
- package/src/lib/cli/resolveCliTarget.ts +48 -0
- package/src/lib/cli/runCli.ts +139 -0
- package/src/lib/cli/runSession.ts +105 -0
- package/src/lib/cli/startLocalInstance.ts +14 -0
- package/src/lib/cli/tokenizeLine.ts +51 -0
- package/src/lib/cli/types/CliManifest.ts +9 -0
- package/src/lib/cli/types/CliManifestEntry.ts +17 -0
- package/src/lib/cli/types/CliTarget.ts +13 -0
- package/src/lib/mcp/annotationsForMethod.ts +29 -0
- package/src/lib/mcp/createMcpResourceServer.ts +101 -0
- package/src/lib/mcp/createMcpServer.ts +42 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
- package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
- package/src/lib/mcp/mcpSurface.ts +265 -0
- package/src/lib/mcp/toolResultFromResponse.ts +66 -0
- package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
- package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
- package/src/lib/mcp/types/McpResourceContents.ts +10 -0
- package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
- package/src/lib/mcp/types/McpResourceServer.ts +12 -0
- package/src/lib/mcp/types/McpServer.ts +9 -0
- package/src/lib/mcp/types/McpServerOptions.ts +16 -0
- package/src/lib/server/AppModule.ts +33 -0
- package/src/lib/server/DELETE.ts +9 -0
- package/src/lib/server/GET.ts +9 -0
- package/src/lib/server/HEAD.ts +9 -0
- package/src/lib/server/PATCH.ts +9 -0
- package/src/lib/server/POST.ts +9 -0
- package/src/lib/server/PUT.ts +9 -0
- package/src/lib/server/agent.ts +76 -0
- package/src/lib/server/appDataDir.ts +15 -0
- package/src/lib/server/cli/buildEnvContent.ts +19 -0
- package/src/lib/server/cli/createTarGz.ts +76 -0
- package/src/lib/server/cli/handleCliDownload.ts +153 -0
- package/src/lib/server/cli/handleCliInstall.ts +37 -0
- package/src/lib/server/cli/installScript.ts +29 -0
- package/src/lib/server/cli/maxSourceMtime.ts +26 -0
- package/src/lib/server/cookies.ts +29 -0
- package/src/lib/server/env.ts +50 -0
- package/src/lib/server/error.ts +70 -0
- package/src/lib/server/json.ts +28 -0
- package/src/lib/server/jsonl.ts +46 -0
- package/src/lib/server/prompts/definePrompt.ts +20 -0
- package/src/lib/server/prompts/promptRegistry.ts +9 -0
- package/src/lib/server/prompts/registerPrompt.ts +6 -0
- package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
- package/src/lib/server/prompts/types/Prompt.ts +13 -0
- package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
- package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
- package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
- package/src/lib/server/redirect.ts +42 -0
- package/src/lib/server/request.ts +18 -0
- package/src/lib/server/rpc/defineVerb.ts +133 -0
- package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
- package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
- package/src/lib/server/rpc/parseArgs.ts +95 -0
- package/src/lib/server/rpc/registerVerb.ts +6 -0
- package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
- package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
- package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
- package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
- package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
- package/src/lib/server/rpc/unprocessed.ts +14 -0
- package/src/lib/server/rpc/verbRegistry.ts +11 -0
- package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
- package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
- package/src/lib/server/runtime/acceptsZstd.ts +8 -0
- package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
- package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
- package/src/lib/server/runtime/containsTraversal.ts +37 -0
- package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
- package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
- package/src/lib/server/runtime/createServer.ts +692 -0
- package/src/lib/server/runtime/devReloadResponse.ts +35 -0
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
- package/src/lib/server/runtime/envSchemaStore.ts +15 -0
- package/src/lib/server/runtime/findOpenPort.ts +35 -0
- package/src/lib/server/runtime/getActiveServer.ts +6 -0
- package/src/lib/server/runtime/globToPathSet.ts +29 -0
- package/src/lib/server/runtime/inProcessServer.ts +20 -0
- package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
- package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
- package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
- package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
- package/src/lib/server/runtime/mimeForExtension.ts +20 -0
- package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
- package/src/lib/server/runtime/parsePort.ts +11 -0
- package/src/lib/server/runtime/registryManifests.ts +66 -0
- package/src/lib/server/runtime/requestContext.ts +5 -0
- package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
- package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
- package/src/lib/server/runtime/serverSlot.ts +13 -0
- package/src/lib/server/runtime/setActiveServer.ts +6 -0
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
- package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
- package/src/lib/server/runtime/streamFromIterator.ts +86 -0
- package/src/lib/server/runtime/streamStash.ts +64 -0
- package/src/lib/server/runtime/types/Assets.ts +1 -0
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
- package/src/lib/server/server.ts +32 -0
- package/src/lib/server/socket.ts +31 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
- package/src/lib/server/sockets/defineSocket.ts +167 -0
- package/src/lib/server/sockets/lookupSocket.ts +6 -0
- package/src/lib/server/sockets/recentHistory.ts +11 -0
- package/src/lib/server/sockets/registerSocket.ts +6 -0
- package/src/lib/server/sockets/socketOperations.ts +35 -0
- package/src/lib/server/sockets/socketRegistry.ts +9 -0
- package/src/lib/server/sockets/types/Socket.ts +21 -0
- package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
- package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
- package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
- package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
- package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
- package/src/lib/server/sse.ts +53 -0
- package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
- package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
- package/src/lib/shared/HttpError.ts +19 -0
- package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
- package/src/lib/shared/activeCacheStore.ts +20 -0
- package/src/lib/shared/appDataDir.ts +34 -0
- package/src/lib/shared/belteImportName.ts +44 -0
- package/src/lib/shared/browserClientFlags.ts +10 -0
- package/src/lib/shared/buildRpcRequest.ts +70 -0
- package/src/lib/shared/bundleLayout.ts +36 -0
- package/src/lib/shared/bundled.ts +34 -0
- package/src/lib/shared/cache.ts +559 -0
- package/src/lib/shared/cacheStoreSlot.ts +16 -0
- package/src/lib/shared/canonicalJson.ts +63 -0
- package/src/lib/shared/carriesBodyArgs.ts +13 -0
- package/src/lib/shared/clearLastConnection.ts +7 -0
- package/src/lib/shared/commandNameForUrl.ts +17 -0
- package/src/lib/shared/createCacheStore.ts +75 -0
- package/src/lib/shared/createPushIterator.ts +93 -0
- package/src/lib/shared/createRemoteFunction.ts +99 -0
- package/src/lib/shared/decodeResponse.ts +47 -0
- package/src/lib/shared/detectTarget.ts +27 -0
- package/src/lib/shared/exeSuffix.ts +9 -0
- package/src/lib/shared/exitOnBuildFailure.ts +17 -0
- package/src/lib/shared/extraForwardHeaders.ts +16 -0
- package/src/lib/shared/fileStem.ts +9 -0
- package/src/lib/shared/findExportCallSite.ts +479 -0
- package/src/lib/shared/forwardHeaders.ts +41 -0
- package/src/lib/shared/getRemoteMeta.ts +5 -0
- package/src/lib/shared/globalCacheStore.ts +15 -0
- package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
- package/src/lib/shared/importNamesToStrip.ts +13 -0
- package/src/lib/shared/invalidateEvent.ts +11 -0
- package/src/lib/shared/isCompileTarget.ts +15 -0
- package/src/lib/shared/isDebugEnabled.ts +23 -0
- package/src/lib/shared/isModuleNotFound.ts +16 -0
- package/src/lib/shared/isReadOnlyMethod.ts +14 -0
- package/src/lib/shared/isStreamingResponse.ts +11 -0
- package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
- package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
- package/src/lib/shared/jsonlErrorFrame.ts +24 -0
- package/src/lib/shared/keyForRemoteCall.ts +29 -0
- package/src/lib/shared/lastConnectionPath.ts +7 -0
- package/src/lib/shared/loadEnvFile.ts +17 -0
- package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
- package/src/lib/shared/loadSvelteConfig.ts +18 -0
- package/src/lib/shared/log.ts +104 -0
- package/src/lib/shared/manifestModule.ts +39 -0
- package/src/lib/shared/memoizeByKey.ts +24 -0
- package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
- package/src/lib/shared/normalizeTarget.ts +10 -0
- package/src/lib/shared/pageUrlForFile.ts +14 -0
- package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
- package/src/lib/shared/parseEnv.ts +30 -0
- package/src/lib/shared/parsePromptMarkdown.ts +34 -0
- package/src/lib/shared/parseRouteSegments.ts +22 -0
- package/src/lib/shared/prepareRpcModule.ts +59 -0
- package/src/lib/shared/prepareSocketModule.ts +49 -0
- package/src/lib/shared/programNameForPackage.ts +14 -0
- package/src/lib/shared/promptNameForFile.ts +10 -0
- package/src/lib/shared/queryStringFromArgs.ts +27 -0
- package/src/lib/shared/readEnvFile.ts +15 -0
- package/src/lib/shared/readLastConnection.ts +18 -0
- package/src/lib/shared/readPackageJson.ts +9 -0
- package/src/lib/shared/recordRemoteMeta.ts +5 -0
- package/src/lib/shared/remoteMetaStore.ts +16 -0
- package/src/lib/shared/resolveClientFlags.ts +20 -0
- package/src/lib/shared/responseErrorText.ts +9 -0
- package/src/lib/shared/rpcUrlForFile.ts +19 -0
- package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
- package/src/lib/shared/serializeEnv.ts +18 -0
- package/src/lib/shared/setCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
- package/src/lib/shared/socketNameForFile.ts +11 -0
- package/src/lib/shared/sseErrorFrame.ts +29 -0
- package/src/lib/shared/streamResponse.ts +169 -0
- package/src/lib/shared/stripImport.ts +27 -0
- package/src/lib/shared/subscribableFromResponse.ts +51 -0
- package/src/lib/shared/toBunRoutePattern.ts +28 -0
- package/src/lib/shared/types/CacheEntry.ts +63 -0
- package/src/lib/shared/types/CacheInvalidation.ts +9 -0
- package/src/lib/shared/types/CacheOptions.ts +33 -0
- package/src/lib/shared/types/CacheSnapshot.ts +16 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
- package/src/lib/shared/types/CacheStore.ts +32 -0
- package/src/lib/shared/types/ClientFlags.ts +11 -0
- package/src/lib/shared/types/CompileTarget.ts +6 -0
- package/src/lib/shared/types/HttpVerb.ts +1 -0
- package/src/lib/shared/types/LastConnection.ts +9 -0
- package/src/lib/shared/types/PromptArgument.ts +12 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
- package/src/lib/shared/types/RemoteFunction.ts +42 -0
- package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
- package/src/lib/shared/types/StreamedResolution.ts +10 -0
- package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
- package/src/lib/shared/types/Subscribable.ts +15 -0
- package/src/lib/shared/types/SvelteConfig.ts +5 -0
- package/src/lib/shared/withJsonSchema.ts +20 -0
- package/src/lib/shared/writeLastConnection.ts +13 -0
- package/src/lib/shared/writeRoutesDts.ts +67 -0
- package/src/lib/test/clearVerbRegistry.ts +11 -0
- package/src/lib/test/createTestClient.ts +78 -0
- package/src/preload.ts +20 -0
- package/src/scaffold.ts +92 -0
- package/src/serverBuildPlugins.ts +25 -0
- package/src/serverEntry.ts +94 -0
- package/src/sveltePlugin.ts +58 -0
- package/src/tailwindStylePreprocessor.ts +62 -0
- package/template/bunfig.toml +4 -0
- package/template/package.json +19 -0
- package/template/src/app.ts +23 -0
- package/template/src/browser/app.css +21 -0
- package/template/src/browser/app.html +24 -0
- package/template/src/browser/pages/about/page.svelte +5 -0
- package/template/src/browser/pages/layout.svelte +26 -0
- package/template/src/browser/pages/page.svelte +20 -0
- package/template/src/bundle/icon.png +0 -0
- package/template/src/cli/banner.txt +3 -0
- package/template/src/cli/footer.txt +1 -0
- package/template/src/server/config.ts +17 -0
- package/template/src/server/rpc/getHello.ts +35 -0
- package/template/svelte.config.js +12 -0
- package/template/tsconfig.json +18 -0
- 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
|
+
}
|
package/src/buildCli.ts
ADDED
|
@@ -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
|
+
}
|
package/src/bundleApp.ts
ADDED
|
@@ -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
|
+
}
|
package/src/cliEntry.ts
ADDED
|
@@ -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 })
|