@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/compile.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { build } from './build.ts'
|
|
2
|
+
import { detectTarget } from './lib/shared/detectTarget.ts'
|
|
3
|
+
import { exeSuffix } from './lib/shared/exeSuffix.ts'
|
|
4
|
+
import { exitOnBuildFailure } from './lib/shared/exitOnBuildFailure.ts'
|
|
5
|
+
import { loadSvelteConfig } from './lib/shared/loadSvelteConfig.ts'
|
|
6
|
+
import { log } from './lib/shared/log.ts'
|
|
7
|
+
import type { CompileTarget } from './lib/shared/types/CompileTarget.ts'
|
|
8
|
+
import { serverBuildPlugins } from './serverBuildPlugins.ts'
|
|
9
|
+
|
|
10
|
+
const SERVER_ENTRY = new URL('./serverEntry.ts', import.meta.url).pathname
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
Produces a standalone Bun executable for the server. Runs the client `build`
|
|
14
|
+
first so the resolver plugin can embed the zstd-compressed assets into
|
|
15
|
+
the binary, then invokes Bun.build in compile mode against the server
|
|
16
|
+
entry. Defaults
|
|
17
|
+
the target to the host platform and appends `.exe` for windows targets.
|
|
18
|
+
Returns the path of the emitted binary; exits the process on build failure.
|
|
19
|
+
*/
|
|
20
|
+
export async function compile({
|
|
21
|
+
cwd = process.cwd(),
|
|
22
|
+
target = detectTarget(),
|
|
23
|
+
outfile,
|
|
24
|
+
buildClient = true,
|
|
25
|
+
}: {
|
|
26
|
+
cwd?: string
|
|
27
|
+
target?: CompileTarget
|
|
28
|
+
outfile?: string
|
|
29
|
+
/*
|
|
30
|
+
Skip the client `build` (which clears dist). Set false when the caller already
|
|
31
|
+
built the platform-independent client once and is compiling several server
|
|
32
|
+
binaries against it — e.g. `belte cli` co-shipping a per-platform server beside
|
|
33
|
+
each CLI binary — so the shared `dist/_app` isn't wiped between targets.
|
|
34
|
+
*/
|
|
35
|
+
buildClient?: boolean
|
|
36
|
+
} = {}): Promise<string> {
|
|
37
|
+
const svelteConfig = await loadSvelteConfig(cwd)
|
|
38
|
+
if (buildClient) {
|
|
39
|
+
await build({ cwd, svelteConfig })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const outPath = outfile ?? `${cwd}/dist/app${exeSuffix(target)}`
|
|
43
|
+
|
|
44
|
+
const result = await Bun.build({
|
|
45
|
+
entrypoints: [SERVER_ENTRY],
|
|
46
|
+
target: 'bun',
|
|
47
|
+
format: 'esm',
|
|
48
|
+
minify: true,
|
|
49
|
+
/*
|
|
50
|
+
Bytecode embeds precompiled JS module metadata directly into the
|
|
51
|
+
standalone binary, dramatically cutting cold-start time for large
|
|
52
|
+
apps. Requires `target: 'bun'` + an explicit `format` because the
|
|
53
|
+
default for `bytecode` alone is CommonJS; we need ESM bytecode.
|
|
54
|
+
*/
|
|
55
|
+
bytecode: true,
|
|
56
|
+
compile: { target, outfile: outPath },
|
|
57
|
+
plugins: serverBuildPlugins({ cwd, svelteConfig, embedAssets: true }),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
exitOnBuildFailure(result)
|
|
61
|
+
|
|
62
|
+
log.success(`compiled standalone binary: ${outPath} (target: ${target})`)
|
|
63
|
+
return outPath
|
|
64
|
+
}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { bindConnectedFlag } from './lib/bundle/bindConnectedFlag.ts'
|
|
4
|
+
import { bindRequestNavigate } from './lib/bundle/bindRequestNavigate.ts'
|
|
5
|
+
import { listenLocalControlServer } from './lib/bundle/listenLocalControlServer.ts'
|
|
6
|
+
import { probeBelteServer } from './lib/bundle/probeBelteServer.ts'
|
|
7
|
+
import { resolveWebviewLib } from './lib/bundle/resolveWebviewLib.ts'
|
|
8
|
+
import { spawnEmbeddedServer } from './lib/bundle/spawnEmbeddedServer.ts'
|
|
9
|
+
import { stableLocalPort } from './lib/bundle/stableLocalPort.ts'
|
|
10
|
+
import { appDataDir } from './lib/shared/appDataDir.ts'
|
|
11
|
+
import { bundleLayout } from './lib/shared/bundleLayout.ts'
|
|
12
|
+
import { clearLastConnection } from './lib/shared/clearLastConnection.ts'
|
|
13
|
+
import { log } from './lib/shared/log.ts'
|
|
14
|
+
import { readEnvFile } from './lib/shared/readEnvFile.ts'
|
|
15
|
+
import { readLastConnection } from './lib/shared/readLastConnection.ts'
|
|
16
|
+
import { serializeEnv } from './lib/shared/serializeEnv.ts'
|
|
17
|
+
import { writeLastConnection } from './lib/shared/writeLastConnection.ts'
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
The bundle's control server, run in a Worker so it owns its own thread.
|
|
21
|
+
|
|
22
|
+
`webview_run` enters a native UI run loop that blocks the launcher's main thread
|
|
23
|
+
indefinitely (the window owns it until close), which freezes the main thread's
|
|
24
|
+
JS event loop. An in-process `Bun.serve` there can never answer a request, so the
|
|
25
|
+
webview pointed at it would only ever see a hung navigation — a blank window.
|
|
26
|
+
|
|
27
|
+
Running the control server on this Worker thread keeps it answering the whole time
|
|
28
|
+
the window is open. It owns the pieces that must live beside it: the embedded
|
|
29
|
+
server child it spawns, and its own FFI handle to the native menu flag (set here
|
|
30
|
+
because the main thread can't process a postMessage while blocked in webview_run,
|
|
31
|
+
yet the flag is a process-global the main-thread menu still reads).
|
|
32
|
+
|
|
33
|
+
Bun does not apply the launcher build's plugins to a worker entry, so this module
|
|
34
|
+
can't import belte's virtual modules (the connect-screen HTML, app title). The
|
|
35
|
+
launcher — which can — passes them in the `init` message; on `shutdown` it has us
|
|
36
|
+
reap the embedded child before the launcher exits.
|
|
37
|
+
|
|
38
|
+
Once connected it also watches the chosen server's liveness — polling its identity
|
|
39
|
+
endpoint — and, when it stops answering, corrects the menu flag and bounces the
|
|
40
|
+
window back to the connect screen, since a dead server (local crash or remote
|
|
41
|
+
outage) otherwise leaves a frozen page and a menu that still claims connected.
|
|
42
|
+
|
|
43
|
+
GET / → the connect screen (title injected at serve time)
|
|
44
|
+
GET /__belte/config → { schema, values } for the first-run config form
|
|
45
|
+
POST /__belte/config → persist the form's answers to the data-dir .env
|
|
46
|
+
POST /connect {url} → record connected, reply { redirect: url }
|
|
47
|
+
POST /start → spawn the server binary, reply { redirect: localUrl }
|
|
48
|
+
GET /__belte/disconnect → reap the child, clear connected
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
Init payload from the launcher, plus the per-run state the handlers close over.
|
|
53
|
+
`configSchema` is the JSON Schema derived from the app's BundleWindow.config
|
|
54
|
+
(undefined when none declared), driving the connect screen's first-run form.
|
|
55
|
+
*/
|
|
56
|
+
type Init = {
|
|
57
|
+
disconnectedHtml: string
|
|
58
|
+
title: string
|
|
59
|
+
programName: string
|
|
60
|
+
configSchema?: Record<string, unknown>
|
|
61
|
+
}
|
|
62
|
+
let disconnectedHtml = ''
|
|
63
|
+
let title = ''
|
|
64
|
+
let programName = ''
|
|
65
|
+
let configSchema: Record<string, unknown> | undefined
|
|
66
|
+
let flag: ReturnType<typeof bindConnectedFlag> | undefined
|
|
67
|
+
let server: ReturnType<typeof listenLocalControlServer> | undefined
|
|
68
|
+
|
|
69
|
+
// The control-server origin (where the connect screen lives) and the webview
|
|
70
|
+
// handle, forwarded by the launcher — together they let the watch bounce a dead
|
|
71
|
+
// window back to the connect screen.
|
|
72
|
+
let controlOrigin = ''
|
|
73
|
+
let navigate: ReturnType<typeof bindRequestNavigate> | undefined
|
|
74
|
+
let webviewHandle: number | undefined
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
Liveness watch over the currently-connected server. A recursive timer (not
|
|
78
|
+
setInterval, so a slow probe never overlaps the next) probes the identity endpoint;
|
|
79
|
+
a couple of consecutive misses — tolerating a transient blip or a quick restart —
|
|
80
|
+
count as a death. Cleared whenever we're not connected.
|
|
81
|
+
*/
|
|
82
|
+
const LIVENESS_INTERVAL_MS = 4000
|
|
83
|
+
const LIVENESS_FAILURE_LIMIT = 2
|
|
84
|
+
let connectedUrl: string | undefined
|
|
85
|
+
let livenessTimer: ReturnType<typeof setTimeout> | undefined
|
|
86
|
+
let livenessFailures = 0
|
|
87
|
+
|
|
88
|
+
// Embedded-server child, spawned on demand by Start server; undefined when none.
|
|
89
|
+
let serverChild: ReturnType<typeof Bun.spawn> | undefined
|
|
90
|
+
|
|
91
|
+
// Reaps the embedded server child if one is running.
|
|
92
|
+
function killServerChild(): void {
|
|
93
|
+
if (serverChild) {
|
|
94
|
+
serverChild.kill()
|
|
95
|
+
serverChild = undefined
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Begin (or restart) watching `url` for liveness once the window points at it.
|
|
100
|
+
function startLivenessWatch(url: string): void {
|
|
101
|
+
stopLivenessWatch()
|
|
102
|
+
connectedUrl = url
|
|
103
|
+
livenessFailures = 0
|
|
104
|
+
livenessTimer = setTimeout(runLivenessProbe, LIVENESS_INTERVAL_MS)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Stop watching — on explicit disconnect, on detected death, or at shutdown.
|
|
108
|
+
function stopLivenessWatch(): void {
|
|
109
|
+
if (livenessTimer) {
|
|
110
|
+
clearTimeout(livenessTimer)
|
|
111
|
+
livenessTimer = undefined
|
|
112
|
+
}
|
|
113
|
+
connectedUrl = undefined
|
|
114
|
+
livenessFailures = 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
One liveness probe of the connected server. Successes reset the miss count;
|
|
119
|
+
LIVENESS_FAILURE_LIMIT consecutive misses declare it dead and hand off to
|
|
120
|
+
handleConnectionLost. Reschedules itself while still connected.
|
|
121
|
+
*/
|
|
122
|
+
async function runLivenessProbe(): Promise<void> {
|
|
123
|
+
const url = connectedUrl
|
|
124
|
+
if (!url) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
const identity = await probeBelteServer(url)
|
|
128
|
+
// A disconnect or reconnect during the await may have moved us on.
|
|
129
|
+
if (connectedUrl !== url) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (identity) {
|
|
133
|
+
livenessFailures = 0
|
|
134
|
+
} else {
|
|
135
|
+
livenessFailures += 1
|
|
136
|
+
if (livenessFailures >= LIVENESS_FAILURE_LIMIT) {
|
|
137
|
+
handleConnectionLost(url)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
livenessTimer = setTimeout(runLivenessProbe, LIVENESS_INTERVAL_MS)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
The connected server stopped answering. Reap any (now-dead) embedded child, clear
|
|
146
|
+
the connected flag so the menu stops claiming connected, and bounce the window
|
|
147
|
+
back to the connect screen with a `lost` notice. The flag flip alone keeps the
|
|
148
|
+
menu honest even when the navigate is a no-op (off macOS, or no handle yet).
|
|
149
|
+
*/
|
|
150
|
+
function handleConnectionLost(url: string): void {
|
|
151
|
+
log.warn(`connected server stopped responding: ${url}`)
|
|
152
|
+
stopLivenessWatch()
|
|
153
|
+
killServerChild()
|
|
154
|
+
flag?.setConnected(false)
|
|
155
|
+
if (webviewHandle !== undefined) {
|
|
156
|
+
navigate?.requestNavigate(webviewHandle, `${controlOrigin}/?action=lost`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
Boots the embedded server via the shared spawn helper and keeps the child so
|
|
162
|
+
killServerChild can reap it. Any previous child is reaped first so only one
|
|
163
|
+
embedded server runs at a time; returns the URL to point the window at.
|
|
164
|
+
*/
|
|
165
|
+
async function startEmbeddedServer(timeoutMs?: number): Promise<string> {
|
|
166
|
+
killServerChild()
|
|
167
|
+
const { url, child } = await spawnEmbeddedServer({ programName, timeoutMs })
|
|
168
|
+
serverChild = child
|
|
169
|
+
return url
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/*
|
|
173
|
+
Where the window should point on launch, resolved before it ever opens so the
|
|
174
|
+
connect screen never flashes. Repeats the last connection from the launcher-owned
|
|
175
|
+
record (which survives relaunch where the embedded server's fresh port can't):
|
|
176
|
+
|
|
177
|
+
- embedded, config complete → boot it and point at the live server
|
|
178
|
+
- embedded, config missing → the connect screen, so the user can configure
|
|
179
|
+
- remote url, still alive → point straight at it
|
|
180
|
+
- remote url, now dead → the connect screen with a `lost` notice
|
|
181
|
+
- nothing recorded → the connect screen
|
|
182
|
+
|
|
183
|
+
Boot is bounded by a short ceiling: a failed or slow boot falls back to the
|
|
184
|
+
connect screen rather than leaving the launcher window-less, and reaps the child
|
|
185
|
+
so a half-started server doesn't hold its port.
|
|
186
|
+
*/
|
|
187
|
+
const AUTO_START_CEILING_MS = 3000
|
|
188
|
+
async function resolveLaunchTarget(): Promise<string> {
|
|
189
|
+
const last = await readLastConnection(programName)
|
|
190
|
+
if (!last) {
|
|
191
|
+
return controlOrigin
|
|
192
|
+
}
|
|
193
|
+
if (last.kind === 'embedded') {
|
|
194
|
+
if (await autoStartBlockedByConfig()) {
|
|
195
|
+
return controlOrigin
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const url = await startEmbeddedServer(AUTO_START_CEILING_MS)
|
|
199
|
+
flag?.setConnected(true)
|
|
200
|
+
startLivenessWatch(url)
|
|
201
|
+
log.info(`resumed embedded server at ${url}`)
|
|
202
|
+
return url
|
|
203
|
+
} catch (error) {
|
|
204
|
+
killServerChild()
|
|
205
|
+
log.warn(`embedded server did not resume: ${String(error)}`)
|
|
206
|
+
return controlOrigin
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const identity = await probeBelteServer(last.url)
|
|
210
|
+
if (identity) {
|
|
211
|
+
flag?.setConnected(true)
|
|
212
|
+
startLivenessWatch(last.url)
|
|
213
|
+
log.info(`reconnected to ${identity.name} at ${last.url}`)
|
|
214
|
+
return last.url
|
|
215
|
+
}
|
|
216
|
+
log.warn(`saved server did not respond: ${last.url}`)
|
|
217
|
+
return `${controlOrigin}/?action=lost`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// True when the app declares required config that nothing yet supplies, so an
|
|
221
|
+
// embedded auto-start would only crash for the lack of it — land on the connect
|
|
222
|
+
// screen (and its setup modal) instead.
|
|
223
|
+
async function autoStartBlockedByConfig(): Promise<boolean> {
|
|
224
|
+
const required = (configSchema?.required as string[] | undefined) ?? []
|
|
225
|
+
if (required.length === 0) {
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
const values = await resolveConfigValues()
|
|
229
|
+
return required.some((key) => !values[key])
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
Injects the app title into the connect-screen HTML just before serving — the build
|
|
234
|
+
left a `<!--belte:connect-config-->` marker in <head>.
|
|
235
|
+
*/
|
|
236
|
+
function renderConnectScreen(): Response {
|
|
237
|
+
const script = `<script>window.__BELTE_TITLE__=${JSON.stringify(title)}</script>`
|
|
238
|
+
const html = disconnectedHtml.replace('<!--belte:connect-config-->', script)
|
|
239
|
+
return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8' } })
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The data-dir `.env` the form writes and the server loads first at boot.
|
|
243
|
+
function dataDirEnvPath(): string {
|
|
244
|
+
return join(appDataDir(programName), '.env')
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// The bundle's shipped `.env` (its default config layer), resolved from the binary
|
|
248
|
+
// directory — same source loadEnvFromBinaryDir reads at boot (dirname of the running
|
|
249
|
+
// binary): beside the binary in the flat layout, under Resources in a `.app`.
|
|
250
|
+
function binaryDirEnvPath(): string {
|
|
251
|
+
return bundleLayout(dirname(process.execPath)).envPath
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
Resolves the value to pre-fill each config field with, following the same
|
|
256
|
+
precedence the server applies below the shell: the user's saved data-dir `.env`,
|
|
257
|
+
then the bundle's shipped binary-dir `.env`, then the schema's own `default`.
|
|
258
|
+
Empty string when nothing supplies it — which is how the form spots an unmet
|
|
259
|
+
required field.
|
|
260
|
+
*/
|
|
261
|
+
async function resolveConfigValues(): Promise<Record<string, string>> {
|
|
262
|
+
const properties = (configSchema?.properties ?? {}) as Record<string, { default?: unknown }>
|
|
263
|
+
// Independent reads — fetch together; precedence is applied in the merge below.
|
|
264
|
+
const [dataDirEnv, binaryDirEnv] = await Promise.all([
|
|
265
|
+
readEnvFile(dataDirEnvPath()),
|
|
266
|
+
readEnvFile(binaryDirEnvPath()),
|
|
267
|
+
])
|
|
268
|
+
return Object.fromEntries(
|
|
269
|
+
Object.keys(properties).map((key) => {
|
|
270
|
+
const fallback = properties[key]?.default
|
|
271
|
+
const value =
|
|
272
|
+
dataDirEnv[key] ??
|
|
273
|
+
binaryDirEnv[key] ??
|
|
274
|
+
(fallback === undefined ? '' : String(fallback))
|
|
275
|
+
return [key, value]
|
|
276
|
+
}),
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/*
|
|
281
|
+
Persists the form's answers to the data-dir `.env`, merged over any existing
|
|
282
|
+
file so keys the form didn't touch survive. Creates the data dir on first run
|
|
283
|
+
(appDataDir only computes the path).
|
|
284
|
+
*/
|
|
285
|
+
async function writeConfig(values: Record<string, string>): Promise<void> {
|
|
286
|
+
const path = dataDirEnvPath()
|
|
287
|
+
const merged = { ...(await readEnvFile(path)), ...values }
|
|
288
|
+
await mkdir(appDataDir(programName), { recursive: true })
|
|
289
|
+
await Bun.write(path, serializeEnv(merged))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// GET /__belte/config — the form's schema + current values, or null schema to skip the gate.
|
|
293
|
+
async function handleConfigGet(): Promise<Response> {
|
|
294
|
+
if (!configSchema) {
|
|
295
|
+
return Response.json({ schema: null, values: {} })
|
|
296
|
+
}
|
|
297
|
+
return Response.json({ schema: configSchema, values: await resolveConfigValues() })
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// POST /__belte/config — persist the form's answers to the data-dir `.env`.
|
|
301
|
+
async function handleConfigPost(request: Request): Promise<Response> {
|
|
302
|
+
const { values } = (await request.json()) as { values: Record<string, string> }
|
|
303
|
+
await writeConfig(values)
|
|
304
|
+
return new Response(undefined, { status: 204 })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// POST /connect — point the window at a remote belte server after probing it.
|
|
308
|
+
async function handleConnect(request: Request): Promise<Response> {
|
|
309
|
+
const { url: target } = (await request.json()) as { url: string }
|
|
310
|
+
// Verify it's actually a belte server before pointing the window at it.
|
|
311
|
+
const identity = await probeBelteServer(target)
|
|
312
|
+
if (!identity) {
|
|
313
|
+
log.warn(`no belte server responded at ${target}`)
|
|
314
|
+
return Response.json({ error: `No belte server responded at ${target}` }, { status: 502 })
|
|
315
|
+
}
|
|
316
|
+
flag?.setConnected(true)
|
|
317
|
+
startLivenessWatch(target)
|
|
318
|
+
// Record the choice so the next launch reconnects here before opening.
|
|
319
|
+
await writeLastConnection(programName, { kind: 'url', url: target })
|
|
320
|
+
log.info(`connecting to ${identity.name} at ${target}`)
|
|
321
|
+
return Response.json({ redirect: target })
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// POST /start — boot the embedded server and point the window at it.
|
|
325
|
+
async function handleStart(): Promise<Response> {
|
|
326
|
+
try {
|
|
327
|
+
const localUrl = await startEmbeddedServer()
|
|
328
|
+
flag?.setConnected(true)
|
|
329
|
+
startLivenessWatch(localUrl)
|
|
330
|
+
// Record the choice so the next launch boots the embedded server first.
|
|
331
|
+
await writeLastConnection(programName, { kind: 'embedded' })
|
|
332
|
+
log.info(`started embedded server at ${localUrl}`)
|
|
333
|
+
return Response.json({ redirect: localUrl })
|
|
334
|
+
} catch (error) {
|
|
335
|
+
killServerChild()
|
|
336
|
+
return Response.json({ error: String(error) }, { status: 500 })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// GET /__belte/disconnect — tear down the embedded server and forget the auto-resume choice.
|
|
341
|
+
async function handleDisconnect(): Promise<Response> {
|
|
342
|
+
stopLivenessWatch()
|
|
343
|
+
killServerChild()
|
|
344
|
+
flag?.setConnected(false)
|
|
345
|
+
await clearLastConnection(programName)
|
|
346
|
+
return new Response(undefined, { status: 204 })
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/*
|
|
350
|
+
The control server's routes, keyed by `${method} ${pathname}` (exact match). The
|
|
351
|
+
connect screen owns localStorage + navigation; this worker owns the embedded-
|
|
352
|
+
server process and the native flag.
|
|
353
|
+
*/
|
|
354
|
+
const controlRoutes: Record<string, (request: Request) => Promise<Response> | Response> = {
|
|
355
|
+
'GET /': () => renderConnectScreen(),
|
|
356
|
+
'GET /__belte/config': handleConfigGet,
|
|
357
|
+
'POST /__belte/config': handleConfigPost,
|
|
358
|
+
'POST /connect': handleConnect,
|
|
359
|
+
'POST /start': handleStart,
|
|
360
|
+
'GET /__belte/disconnect': handleDisconnect,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function handleControlRequest(request: Request): Promise<Response> | Response {
|
|
364
|
+
const { pathname } = new URL(request.url)
|
|
365
|
+
const route = controlRoutes[`${request.method} ${pathname}`]
|
|
366
|
+
return route ? route(request) : new Response('not found', { status: 404 })
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/*
|
|
370
|
+
Bind the control server to 127.0.0.1 literally (not `localhost`) so the webview
|
|
371
|
+
reaches it without any IPv4/IPv6 name-resolution ambiguity, open the native flag
|
|
372
|
+
handle, then resolve where the window should open before handing back. Resolving
|
|
373
|
+
the launch target here — booting/probing the last connection before `ready` — is
|
|
374
|
+
what lets the launcher open the window straight at the live server, so the
|
|
375
|
+
connect screen never flashes; only an unconfigured, failed, or absent resume
|
|
376
|
+
falls back to it. The launcher gets both `origin` (for the File-menu actions) and
|
|
377
|
+
`target` (where to point the window now).
|
|
378
|
+
*/
|
|
379
|
+
async function start(init: Init): Promise<void> {
|
|
380
|
+
disconnectedHtml = init.disconnectedHtml
|
|
381
|
+
title = init.title
|
|
382
|
+
programName = init.programName
|
|
383
|
+
configSchema = init.configSchema
|
|
384
|
+
const libPath = await resolveWebviewLib()
|
|
385
|
+
flag = bindConnectedFlag(libPath)
|
|
386
|
+
navigate = bindRequestNavigate(libPath)
|
|
387
|
+
server = listenLocalControlServer(stableLocalPort(init.programName), handleControlRequest)
|
|
388
|
+
controlOrigin = `http://127.0.0.1:${server.port}`
|
|
389
|
+
log.info(`${title} control server listening at ${controlOrigin}`)
|
|
390
|
+
const target = await resolveLaunchTarget()
|
|
391
|
+
self.postMessage({ type: 'ready', origin: controlOrigin, target })
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Reap the child + release the server and FFI handles, then confirm so the
|
|
395
|
+
// launcher can exit cleanly.
|
|
396
|
+
function shutdown(): void {
|
|
397
|
+
stopLivenessWatch()
|
|
398
|
+
killServerChild()
|
|
399
|
+
server?.stop(true)
|
|
400
|
+
flag?.close()
|
|
401
|
+
navigate?.close()
|
|
402
|
+
self.postMessage({ type: 'shutdownDone' })
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/*
|
|
406
|
+
The launcher drives the lifecycle: `init` (with the data this worker can't import)
|
|
407
|
+
starts the server, `window` forwards the webview handle the liveness watch needs to
|
|
408
|
+
navigate, and `shutdown` tears it all down once the window closes.
|
|
409
|
+
*/
|
|
410
|
+
self.addEventListener('message', (event: MessageEvent) => {
|
|
411
|
+
const data = event.data as
|
|
412
|
+
| { type: 'init'; init: Init }
|
|
413
|
+
| { type: 'window'; handle: number }
|
|
414
|
+
| { type: 'shutdown' }
|
|
415
|
+
if (data.type === 'init') {
|
|
416
|
+
void start(data.init)
|
|
417
|
+
} else if (data.type === 'window') {
|
|
418
|
+
webviewHandle = data.handle
|
|
419
|
+
} else if (data.type === 'shutdown') {
|
|
420
|
+
shutdown()
|
|
421
|
+
}
|
|
422
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { BunPlugin } from 'bun'
|
|
2
|
+
|
|
3
|
+
type ExportEntry = string | { [condition: string]: ExportEntry }
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Walks a package.json `exports` entry, returning the first leaf string that
|
|
7
|
+
matches the supplied condition list in order. Returns undefined when no
|
|
8
|
+
branch resolves.
|
|
9
|
+
*/
|
|
10
|
+
function pickExport(entry: ExportEntry, conditions: string[]): string | undefined {
|
|
11
|
+
if (typeof entry === 'string') {
|
|
12
|
+
return entry
|
|
13
|
+
}
|
|
14
|
+
for (const condition of conditions) {
|
|
15
|
+
if (entry[condition]) {
|
|
16
|
+
const resolved = pickExport(entry[condition], conditions)
|
|
17
|
+
if (resolved) {
|
|
18
|
+
return resolved
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Forces every `import 'svelte/...'` (from belte's own source, the consumer's
|
|
27
|
+
source, or any transitive dep) to resolve against the consumer app's svelte
|
|
28
|
+
install, picking the export condition that matches the build target.
|
|
29
|
+
Without this, belte's symlinked source can pick up a second svelte from its
|
|
30
|
+
install location, ship both runtimes, and break hydration. Shared by the
|
|
31
|
+
client build and the bundle connect-screen build.
|
|
32
|
+
*/
|
|
33
|
+
export function dedupeSveltePlugin({
|
|
34
|
+
cwd,
|
|
35
|
+
conditions,
|
|
36
|
+
}: {
|
|
37
|
+
cwd: string
|
|
38
|
+
conditions: string[]
|
|
39
|
+
}): BunPlugin {
|
|
40
|
+
const consumerSvelte = `${cwd}/node_modules/svelte`
|
|
41
|
+
return {
|
|
42
|
+
name: 'belte-dedupe-svelte',
|
|
43
|
+
async setup(build) {
|
|
44
|
+
const pkgFile = Bun.file(`${consumerSvelte}/package.json`)
|
|
45
|
+
if (!(await pkgFile.exists())) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
const consumerPackage = (await pkgFile.json()) as {
|
|
49
|
+
exports: Record<string, ExportEntry>
|
|
50
|
+
}
|
|
51
|
+
build.onResolve({ filter: /^svelte(\/.*)?$/ }, (args) => {
|
|
52
|
+
const subpath =
|
|
53
|
+
args.path === 'svelte' ? '.' : `.${args.path.slice('svelte'.length)}`
|
|
54
|
+
const entry = consumerPackage.exports[subpath]
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
58
|
+
const resolvedFile = pickExport(entry, conditions)
|
|
59
|
+
if (!resolvedFile) {
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
return { path: `${consumerSvelte}/${resolvedFile.replace(/^\.\//, '')}` }
|
|
63
|
+
})
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|