@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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '../shared/types/StandardSchemaV1.ts'
|
|
2
|
+
import type { BundleMenu } from './BundleMenu.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
User-authored bundle window configuration, default-exported from an
|
|
6
|
+
optional `src/bundle/window.ts`. Baked into the launcher at build time
|
|
7
|
+
(via the `belte:bundle-window` virtual) and read directly in dev. Every
|
|
8
|
+
field is optional — the launcher falls back to the program name for the
|
|
9
|
+
title and to openWebview's defaults for size.
|
|
10
|
+
|
|
11
|
+
The standard App/Edit/Window menus (Quit, copy/paste, minimize/close) plus the
|
|
12
|
+
built-in File menu (Start server / Connect / Disconnect) are always installed.
|
|
13
|
+
`menu` adds custom top-level menus between the Edit and Window menus; their items
|
|
14
|
+
emit `belte:menu` events the app handles. See BundleMenuItem.
|
|
15
|
+
*/
|
|
16
|
+
export type BundleWindow = {
|
|
17
|
+
title?: string
|
|
18
|
+
width?: number
|
|
19
|
+
height?: number
|
|
20
|
+
menu?: BundleMenu[]
|
|
21
|
+
/*
|
|
22
|
+
Overrides the bundle setup form's schema. By default the form is derived from
|
|
23
|
+
src/server/config.ts's env schema, so one declaration drives both boot
|
|
24
|
+
validation and the form — set this only when the bundle form should differ
|
|
25
|
+
from the env schema (it replaces, not merges; compose with the env schema
|
|
26
|
+
yourself via its `.extend(...)` if you want both).
|
|
27
|
+
|
|
28
|
+
A Standard Schema (the same kind belte accepts for RPC/MCP). Its JSON Schema
|
|
29
|
+
drives the connect screen's first-run form, shown as a modal when Start is
|
|
30
|
+
clicked with a required key still unset; the user's answers persist to the
|
|
31
|
+
data-dir `.env` the server loads at boot. Each property maps to one env var of
|
|
32
|
+
the same name; `title` is the field label, `description` the hint,
|
|
33
|
+
`format: 'password'` masks the input, and `default` pre-fills it.
|
|
34
|
+
*/
|
|
35
|
+
config?: StandardSchemaV1
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Revision of belte's own contribution to the compiled webview library — the
|
|
3
|
+
native shim sources linked in beside the vendored header (e.g. belteMenu.mm)
|
|
4
|
+
and the flags buildWebviewLib compiles them with. It participates in the build
|
|
5
|
+
cache key alongside the upstream version, so changing belte's native build
|
|
6
|
+
selects a fresh cache path and bypasses any library built before the change.
|
|
7
|
+
Bump this whenever the shim sources or their compile invocation change.
|
|
8
|
+
*/
|
|
9
|
+
export const WEBVIEW_BUILD_REVISION = 9
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Upstream `webview/webview` release the vendored `native/webview.h` is taken
|
|
3
|
+
from (https://github.com/webview/webview, MIT). Used to namespace the build
|
|
4
|
+
cache so bumping the header naturally bypasses any previously built library.
|
|
5
|
+
Bump this whenever `native/webview.h` is re-vendored.
|
|
6
|
+
*/
|
|
7
|
+
export const WEBVIEW_VERSION = '0.12.0'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { dlopen, FFIType } from 'bun:ffi'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Binds belte_set_connected — the native flag the macOS Server menu's
|
|
5
|
+
validateMenuItem: reads to enable/disable Start/Disconnect. The symbol is
|
|
6
|
+
compiled only into the macOS lib (the Cocoa menu shim), so a failed lookup
|
|
7
|
+
degrades to a no-op setter rather than throwing on Linux/Windows.
|
|
8
|
+
|
|
9
|
+
The flag is a process-global, which is what lets the bundle's control server set
|
|
10
|
+
it from off the main thread: that server runs in a Worker because `webview_run`
|
|
11
|
+
blocks the main thread's JS event loop, yet the main-thread menu still reads the
|
|
12
|
+
same value through the shared dylib image. The returned close releases the handle.
|
|
13
|
+
*/
|
|
14
|
+
export function bindConnectedFlag(libPath: string): {
|
|
15
|
+
setConnected: (connected: boolean) => void
|
|
16
|
+
close: () => void
|
|
17
|
+
} {
|
|
18
|
+
try {
|
|
19
|
+
const lib = dlopen(libPath, {
|
|
20
|
+
belte_set_connected: { args: [FFIType.i32], returns: FFIType.void },
|
|
21
|
+
})
|
|
22
|
+
return {
|
|
23
|
+
setConnected: (connected) => lib.symbols.belte_set_connected(connected ? 1 : 0),
|
|
24
|
+
close: () => lib.close(),
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
return { setConnected: () => {}, close: () => {} }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { dlopen, FFIType } from 'bun:ffi'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Binds belte_request_navigate — points the live webview window at a URL from any
|
|
5
|
+
thread by hopping onto the UI thread via webview_dispatch. The launcher's control
|
|
6
|
+
server runs in a Worker (off the main thread that webview_run blocks), so when it
|
|
7
|
+
detects the connected server has died it uses this to bounce the window back to the
|
|
8
|
+
connect screen. macOS-only symbol (the Cocoa shim), so a failed lookup degrades to
|
|
9
|
+
a no-op — elsewhere the worker can still correct the menu flag, just not the window.
|
|
10
|
+
|
|
11
|
+
`handle` is the webview pointer created on the main thread, forwarded to the worker
|
|
12
|
+
as a number; bun:ffi accepts it for a ptr argument from either thread because the
|
|
13
|
+
pointer addresses the same process heap.
|
|
14
|
+
*/
|
|
15
|
+
export function bindRequestNavigate(libPath: string): {
|
|
16
|
+
requestNavigate: (handle: number, url: string) => void
|
|
17
|
+
close: () => void
|
|
18
|
+
} {
|
|
19
|
+
try {
|
|
20
|
+
const lib = dlopen(libPath, {
|
|
21
|
+
belte_request_navigate: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
|
|
22
|
+
})
|
|
23
|
+
return {
|
|
24
|
+
requestNavigate: (handle, url) =>
|
|
25
|
+
lib.symbols.belte_request_navigate(handle, new TextEncoder().encode(`${url}\0`)),
|
|
26
|
+
close: () => lib.close(),
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
return { requestNavigate: () => {}, close: () => {} }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { dirname } from 'node:path'
|
|
2
|
+
import { log } from '../shared/log.ts'
|
|
3
|
+
import { WEBVIEW_VERSION } from './WEBVIEW_VERSION.ts'
|
|
4
|
+
import { webviewCachePath } from './webviewCachePath.ts'
|
|
5
|
+
|
|
6
|
+
// Vendored upstream amalgamation; the host compiler turns it into a lib.
|
|
7
|
+
const HEADER = new URL('./native/webview.h', import.meta.url).pathname
|
|
8
|
+
|
|
9
|
+
// belte's own native shim, linked into the same dylib on macOS to add the
|
|
10
|
+
// standard application menu bar the upstream webview omits.
|
|
11
|
+
const MAC_MENU_SOURCE = new URL('./native/belteMenu.mm', import.meta.url).pathname
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
Linux GTK/WebKit pkg-config module sets, newest first. The vendored header
|
|
15
|
+
auto-selects the GTK4 or GTK3 backend from GTK_MAJOR_VERSION in the include
|
|
16
|
+
path, so supplying the right cflags/libs is all that's needed — no backend
|
|
17
|
+
macro. The first set whose packages are installed wins.
|
|
18
|
+
*/
|
|
19
|
+
const LINUX_PKG_SETS = [
|
|
20
|
+
['gtk4', 'webkitgtk-6.0'],
|
|
21
|
+
['gtk+-3.0', 'webkit2gtk-4.1'],
|
|
22
|
+
['gtk+-3.0', 'webkit2gtk-4.0'],
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Compiles the vendored webview header into a native shared library for the
|
|
27
|
+
host platform and caches it (webviewCachePath), so `belte bundle` needs
|
|
28
|
+
no prebuilt binary and no third-party webview package — just
|
|
29
|
+
the platform's C++ toolchain. Returns the cached path. Throws actionable
|
|
30
|
+
guidance when the toolchain or native webview dev packages are missing,
|
|
31
|
+
rather than letting the compiler fail opaquely.
|
|
32
|
+
*/
|
|
33
|
+
export async function buildWebviewLib(): Promise<string> {
|
|
34
|
+
const outfile = webviewCachePath()
|
|
35
|
+
await Bun.$`mkdir -p ${dirname(outfile)}`.quiet()
|
|
36
|
+
log.info(`building webview ${WEBVIEW_VERSION} for ${process.platform}-${process.arch}…`)
|
|
37
|
+
|
|
38
|
+
if (process.platform === 'darwin') {
|
|
39
|
+
await compileDarwin(outfile)
|
|
40
|
+
} else if (process.platform === 'linux') {
|
|
41
|
+
await compileLinux(outfile)
|
|
42
|
+
} else {
|
|
43
|
+
/*
|
|
44
|
+
Windows needs MSVC + the WebView2 SDK header (WebView2.h), which
|
|
45
|
+
isn't a turnkey shell invocation. Until that build path lands,
|
|
46
|
+
point Windows users at the explicit-path escape hatch.
|
|
47
|
+
*/
|
|
48
|
+
throw new Error(
|
|
49
|
+
`[belte] building the webview library on ${process.platform} isn't supported yet. ` +
|
|
50
|
+
'Set BELTE_WEBVIEW_LIB to a prebuilt webview library to continue.',
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log.success(`built webview library: ${outfile}`)
|
|
55
|
+
return outfile
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// macOS: clang against the WebKit + Cocoa frameworks (always present with the
|
|
59
|
+
// Command Line Tools), linking belte's Objective-C++ menu shim into the same
|
|
60
|
+
// dylib. The `-x` flags switch the input language per file: the vendored
|
|
61
|
+
// header compiles as C++ (it uses the C objc runtime, not objc syntax), the
|
|
62
|
+
// shim as Objective-C++. Maps a missing compiler to the install hint.
|
|
63
|
+
async function compileDarwin(outfile: string): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
await Bun.$`clang++ -std=c++17 -DWEBVIEW_BUILD_SHARED -fvisibility=hidden -shared -framework WebKit -framework Cocoa -x c++ ${HEADER} -x objective-c++ ${MAC_MENU_SOURCE} -o ${outfile}`.quiet()
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'[belte] failed to compile the webview library. Install the Xcode Command ' +
|
|
69
|
+
'Line Tools with `xcode-select --install` and try again.\n' +
|
|
70
|
+
describeShellError(error),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Linux: detect an installed GTK/WebKit set via pkg-config, then compile a
|
|
76
|
+
// position-independent shared object. Maps missing packages to install hints.
|
|
77
|
+
async function compileLinux(outfile: string): Promise<void> {
|
|
78
|
+
const flags = await linuxPkgFlags()
|
|
79
|
+
try {
|
|
80
|
+
await Bun.$`c++ -std=c++17 -DWEBVIEW_BUILD_SHARED -fvisibility=hidden -fPIC -shared -x c++ ${HEADER} ${flags} -o ${outfile}`.quiet()
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
'[belte] failed to compile the webview library. Ensure a C++ compiler ' +
|
|
84
|
+
'(e.g. `build-essential`) is installed.\n' +
|
|
85
|
+
describeShellError(error),
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Returns combined cflags + libs for the first available GTK/WebKit set, or
|
|
91
|
+
// throws with the package names to install when none resolve.
|
|
92
|
+
async function linuxPkgFlags(): Promise<string[]> {
|
|
93
|
+
for (const modules of LINUX_PKG_SETS) {
|
|
94
|
+
const probe = await Bun.$`pkg-config --exists ${modules}`.nothrow().quiet()
|
|
95
|
+
if (probe.exitCode === 0) {
|
|
96
|
+
const flags = await Bun.$`pkg-config --cflags --libs ${modules}`.quiet().text()
|
|
97
|
+
return flags.trim().split(/\s+/)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw new Error(
|
|
101
|
+
'[belte] no GTK/WebKit development packages found. Install one set, e.g. ' +
|
|
102
|
+
'`libgtk-4-dev libwebkitgtk-6.0-dev` or `libgtk-3-dev libwebkit2gtk-4.1-dev`, ' +
|
|
103
|
+
'or set BELTE_WEBVIEW_LIB to a prebuilt webview library.',
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Surfaces the compiler's own stderr when a Bun shell command throws.
|
|
108
|
+
function describeShellError(error: unknown): string {
|
|
109
|
+
const stderr = (error as { stderr?: Uint8Array }).stderr
|
|
110
|
+
return stderr ? new TextDecoder().decode(stderr) : String(error)
|
|
111
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tailwind entry for the bundle connect screen. `@import "tailwindcss"` pulls the
|
|
3
|
+
engine in; the explicit `@source` guarantees the default component is scanned for
|
|
4
|
+
utility classes even if automatic source detection misses it. bun-plugin-tailwind
|
|
5
|
+
compiles this at build time; buildDisconnected inlines the result into the served
|
|
6
|
+
HTML so the screen needs zero external requests.
|
|
7
|
+
*/
|
|
8
|
+
@import 'tailwindcss';
|
|
9
|
+
@source './disconnected.svelte';
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
Default bundle connect screen. The launcher serves this (with the logo baked in
|
|
4
|
+
at build time and the app title injected at runtime) instead of a blank window,
|
|
5
|
+
and overriding it is a matter of dropping a `src/bundle/disconnected.svelte`.
|
|
6
|
+
|
|
7
|
+
It both connects to a remote server by URL and boots the embedded server, talking
|
|
8
|
+
to the launcher's in-process control server: POST /connect and POST /start each
|
|
9
|
+
reply with a `{ redirect }` the page follows, while the launcher records the
|
|
10
|
+
connection so the native File menu's enabled state stays authoritative.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
The last remote URL that successfully connected — only prefills the form's input
|
|
15
|
+
on a later visit; it never drives a reconnect (the launcher owns auto-resume now,
|
|
16
|
+
deciding before the window even opens). Survives disconnect and quitting.
|
|
17
|
+
*/
|
|
18
|
+
const LAST_URL_KEY = 'belte:last-server-url'
|
|
19
|
+
|
|
20
|
+
// Injected globals: app title from the launcher, logo data URI from the build.
|
|
21
|
+
const heading = (globalThis as { __BELTE_TITLE__?: string }).__BELTE_TITLE__ ?? 'belte app'
|
|
22
|
+
const logo = (globalThis as { __BELTE_LOGO__?: string }).__BELTE_LOGO__
|
|
23
|
+
|
|
24
|
+
const placeholder = 'https://example.com'
|
|
25
|
+
|
|
26
|
+
// Prefill the form with the last server we connected to, from any prior launch.
|
|
27
|
+
let url = $state(localStorage.getItem(LAST_URL_KEY) ?? '')
|
|
28
|
+
let error = $state<string | undefined>(undefined)
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
`?action=` set by the File menu (Start/Disconnect) or the launcher when a live
|
|
32
|
+
connection dies (`lost`). Auto-resume of a saved connection now happens in the
|
|
33
|
+
launcher before the window opens, so this screen only ever loads as a real
|
|
34
|
+
destination — there's no no-action auto-resume left to handle here.
|
|
35
|
+
*/
|
|
36
|
+
const launchAction = new URLSearchParams(location.search).get('action')
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
Two phases so the screen never flashes before a redirect. A menu Start may boot
|
|
40
|
+
straight through, so it opens on a neutral splash; every other entry is a genuine
|
|
41
|
+
destination, so the connect screen shows immediately. Boot/connect re-enter the
|
|
42
|
+
splash so a redirect (including after saving config) never flashes the form.
|
|
43
|
+
*/
|
|
44
|
+
let phase = $state<'splash' | 'connect'>(launchAction === 'start' ? 'splash' : 'connect')
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
First-run config form, surfaced as a modal only when Start is clicked (or
|
|
48
|
+
auto-start fires) with a required key still unset. Fields are derived from the
|
|
49
|
+
app's config JSON Schema served by the launcher; answers post back to the
|
|
50
|
+
data-dir `.env` the embedded server loads at boot.
|
|
51
|
+
*/
|
|
52
|
+
type ConfigField = {
|
|
53
|
+
key: string
|
|
54
|
+
label: string
|
|
55
|
+
description?: string
|
|
56
|
+
inputType: 'text' | 'password' | 'number' | 'checkbox'
|
|
57
|
+
required: boolean
|
|
58
|
+
}
|
|
59
|
+
let configFields = $state<ConfigField[]>([])
|
|
60
|
+
let configValues = $state<Record<string, string>>({})
|
|
61
|
+
let showConfig = $state(false)
|
|
62
|
+
let savingConfig = $state(false)
|
|
63
|
+
// Every required field has a value — gates the modal's Save button.
|
|
64
|
+
const canSaveConfig = $derived(
|
|
65
|
+
configFields.every(
|
|
66
|
+
(field) =>
|
|
67
|
+
!field.required ||
|
|
68
|
+
field.inputType === 'checkbox' ||
|
|
69
|
+
(configValues[field.key] ?? '').trim() !== '',
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
Interpret the boot intent once on load. `?action=` is set by the native File
|
|
75
|
+
menu's navigate items (or the launcher when a live connection dies); absent it, a
|
|
76
|
+
remembered server reconnects automatically:
|
|
77
|
+
- start → boot the embedded server (matches the Start Server menu item).
|
|
78
|
+
- lost → the connected server stopped responding; explain and wait (the
|
|
79
|
+
form is already prefilled with the last URL for a one-click retry).
|
|
80
|
+
- disconnect → forget the URL + tear down any embedded server, stay here so the
|
|
81
|
+
form is the place to point at another server.
|
|
82
|
+
- (none) → reconnect to the saved server if there is one.
|
|
83
|
+
*/
|
|
84
|
+
$effect(() => {
|
|
85
|
+
if (launchAction === 'start') {
|
|
86
|
+
// File-menu Start Server is an explicit click → run the Start flow.
|
|
87
|
+
void start()
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
if (launchAction === 'lost') {
|
|
91
|
+
error = 'The server stopped responding.'
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (launchAction === 'disconnect') {
|
|
95
|
+
// Have the launcher forget the auto-resume choice and reap any embedded
|
|
96
|
+
// server; LAST_URL_KEY stays so the form is still prefilled to reconnect.
|
|
97
|
+
void fetch('/__belte/disconnect').catch(() => {})
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
Ask the launcher to connect to a server by URL. It verifies the URL really is a
|
|
103
|
+
belte server — POST /connect probes the target's identity endpoint — and flips the
|
|
104
|
+
native menu's connected flag before replying with the `{ redirect }` to follow; a
|
|
105
|
+
URL that isn't a belte server comes back as an error shown below the form. Only a
|
|
106
|
+
confirmed URL is remembered, so a relaunch never auto-retries a dead or wrong
|
|
107
|
+
address. The saved-URL reconnect path runs through here too.
|
|
108
|
+
*/
|
|
109
|
+
async function connect(target: string = url.trim()): Promise<void> {
|
|
110
|
+
const cleaned = target.trim()
|
|
111
|
+
if (!cleaned) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
error = undefined
|
|
115
|
+
// Hide the form while connecting so a successful redirect doesn't flash it.
|
|
116
|
+
phase = 'splash'
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch('/connect', {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'content-type': 'application/json' },
|
|
121
|
+
body: JSON.stringify({ url: cleaned }),
|
|
122
|
+
})
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const body = (await response.json()) as { error?: string }
|
|
125
|
+
throw new Error(body.error ?? `connect failed (${response.status})`)
|
|
126
|
+
}
|
|
127
|
+
const { redirect } = (await response.json()) as { redirect: string }
|
|
128
|
+
// Prefill the form with this server on a later visit (the launcher records
|
|
129
|
+
// the auto-resume choice itself, on the /connect it just handled).
|
|
130
|
+
localStorage.setItem(LAST_URL_KEY, cleaned)
|
|
131
|
+
location.href = redirect
|
|
132
|
+
} catch (cause) {
|
|
133
|
+
error = `Could not connect: ${String(cause)}`
|
|
134
|
+
// Failed — bring the form back so the error and a retry are visible.
|
|
135
|
+
phase = 'connect'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
Start, always an explicit click (button or File-menu) — auto-resume happens in
|
|
141
|
+
the launcher before the window opens, so this is never a launch path. Asks the
|
|
142
|
+
launcher what config the app needs: if it declares any, open the modal (prefilled
|
|
143
|
+
with the last-used values) so the user can review or change settings before
|
|
144
|
+
booting — re-running Start after a disconnect is how you reconfigure. With no
|
|
145
|
+
config schema, boot straight through. The modal's save path resumes the boot.
|
|
146
|
+
*/
|
|
147
|
+
async function start(): Promise<void> {
|
|
148
|
+
error = undefined
|
|
149
|
+
const config = await loadConfig().catch(() => undefined)
|
|
150
|
+
if (config) {
|
|
151
|
+
configFields = config.fields
|
|
152
|
+
configValues = { ...config.values }
|
|
153
|
+
// Reveal the connect screen as the modal's backdrop.
|
|
154
|
+
phase = 'connect'
|
|
155
|
+
showConfig = true
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
await boot()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Boot the embedded server via the launcher, then follow it once it answers.
|
|
162
|
+
async function boot(): Promise<void> {
|
|
163
|
+
// Splash while booting so the connect screen doesn't flash before the redirect
|
|
164
|
+
// (including straight after saving config).
|
|
165
|
+
phase = 'splash'
|
|
166
|
+
try {
|
|
167
|
+
const response = await fetch('/start', { method: 'POST' })
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const body = (await response.json()) as { error?: string }
|
|
170
|
+
throw new Error(body.error ?? `start failed (${response.status})`)
|
|
171
|
+
}
|
|
172
|
+
const { redirect } = (await response.json()) as { redirect: string }
|
|
173
|
+
location.href = redirect
|
|
174
|
+
} catch (cause) {
|
|
175
|
+
error = `Could not start the server: ${String(cause)}`
|
|
176
|
+
// Boot failed — bring the connect screen back to show the error.
|
|
177
|
+
phase = 'connect'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
Fetches the app's config schema + resolved current values from the launcher and
|
|
183
|
+
turns the JSON Schema into render-ready fields. Returns undefined when no schema
|
|
184
|
+
is declared, so Start never gates.
|
|
185
|
+
*/
|
|
186
|
+
async function loadConfig(): Promise<
|
|
187
|
+
{ fields: ConfigField[]; values: Record<string, string> } | undefined
|
|
188
|
+
> {
|
|
189
|
+
const response = await fetch('/__belte/config')
|
|
190
|
+
const { schema, values } = (await response.json()) as {
|
|
191
|
+
schema: Record<string, unknown> | null
|
|
192
|
+
values: Record<string, string>
|
|
193
|
+
}
|
|
194
|
+
if (!schema) {
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
return { fields: fieldsFromSchema(schema), values: values ?? {} }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Derives one render-ready field per JSON Schema property, reusing the standard
|
|
201
|
+
// slots: `title` → label, `description` → hint, `format`/`type` → input kind.
|
|
202
|
+
function fieldsFromSchema(schema: Record<string, unknown>): ConfigField[] {
|
|
203
|
+
const properties = (schema.properties ?? {}) as Record<string, Record<string, unknown>>
|
|
204
|
+
const required = new Set((schema.required as string[]) ?? [])
|
|
205
|
+
return Object.entries(properties).map(([key, property]) => ({
|
|
206
|
+
key,
|
|
207
|
+
label: (property.title as string) ?? key,
|
|
208
|
+
description: property.description as string | undefined,
|
|
209
|
+
inputType: inputType(property),
|
|
210
|
+
required: required.has(key),
|
|
211
|
+
}))
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Maps a JSON Schema property to an HTML input kind (directory falls back to text
|
|
215
|
+
// until a native picker exists).
|
|
216
|
+
function inputType(property: Record<string, unknown>): ConfigField['inputType'] {
|
|
217
|
+
if (property.type === 'boolean') {
|
|
218
|
+
return 'checkbox'
|
|
219
|
+
}
|
|
220
|
+
if (property.type === 'number' || property.type === 'integer') {
|
|
221
|
+
return 'number'
|
|
222
|
+
}
|
|
223
|
+
if (property.format === 'password') {
|
|
224
|
+
return 'password'
|
|
225
|
+
}
|
|
226
|
+
return 'text'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Persist the form's answers to the data-dir `.env`, then resume the boot.
|
|
230
|
+
async function saveConfig(): Promise<void> {
|
|
231
|
+
error = undefined
|
|
232
|
+
savingConfig = true
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch('/__belte/config', {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: { 'content-type': 'application/json' },
|
|
237
|
+
body: JSON.stringify({ values: configValues }),
|
|
238
|
+
})
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new Error(`save failed (${response.status})`)
|
|
241
|
+
}
|
|
242
|
+
showConfig = false
|
|
243
|
+
savingConfig = false
|
|
244
|
+
await boot()
|
|
245
|
+
} catch (cause) {
|
|
246
|
+
error = `Could not save settings: ${String(cause)}`
|
|
247
|
+
savingConfig = false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
{#if phase === 'splash'}
|
|
253
|
+
<!-- Neutral splash shown while an auto-start/auto-reconnect resolves, so the
|
|
254
|
+
connect screen never flashes before it redirects. Same background as the card. -->
|
|
255
|
+
<div class="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-950">
|
|
256
|
+
{#if logo}
|
|
257
|
+
<img src={logo} alt="" class="h-16 w-16 rounded-xl object-contain opacity-90">
|
|
258
|
+
{/if}
|
|
259
|
+
</div>
|
|
260
|
+
{:else}
|
|
261
|
+
<main
|
|
262
|
+
class="flex min-h-screen items-center justify-center bg-gray-50 p-6 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
|
|
263
|
+
<div
|
|
264
|
+
class="w-full max-w-sm rounded-2xl bg-white p-8 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-gray-800">
|
|
265
|
+
{#if logo}
|
|
266
|
+
<img src={logo} alt="" class="mx-auto mb-5 h-16 w-16 rounded-xl object-contain">
|
|
267
|
+
{/if}
|
|
268
|
+
<h1 class="mb-6 text-center text-xl font-semibold tracking-tight">{heading}</h1>
|
|
269
|
+
|
|
270
|
+
<form
|
|
271
|
+
class="flex flex-col gap-3"
|
|
272
|
+
onsubmit={(event) => {
|
|
273
|
+
event.preventDefault()
|
|
274
|
+
void connect()
|
|
275
|
+
}}>
|
|
276
|
+
<input
|
|
277
|
+
type="url"
|
|
278
|
+
bind:value={url}
|
|
279
|
+
{placeholder}
|
|
280
|
+
autocomplete="url"
|
|
281
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900 focus:ring-1 focus:ring-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:focus:border-gray-100 dark:focus:ring-gray-100">
|
|
282
|
+
<button
|
|
283
|
+
type="submit"
|
|
284
|
+
class="w-full rounded-lg bg-gray-900 px-3 py-2 text-sm font-medium text-white hover:bg-gray-700 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-300">
|
|
285
|
+
Connect
|
|
286
|
+
</button>
|
|
287
|
+
</form>
|
|
288
|
+
|
|
289
|
+
<div class="my-5 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
|
|
290
|
+
<span class="h-px flex-1 bg-gray-200 dark:bg-gray-800"></span>
|
|
291
|
+
or
|
|
292
|
+
<span class="h-px flex-1 bg-gray-200 dark:bg-gray-800"></span>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
onclick={() => void start()}
|
|
298
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
|
|
299
|
+
Start server
|
|
300
|
+
</button>
|
|
301
|
+
|
|
302
|
+
{#if error}
|
|
303
|
+
<p class="mt-4 text-center text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
304
|
+
{/if}
|
|
305
|
+
|
|
306
|
+
<p class="mt-8 text-center text-xs text-gray-400 dark:text-gray-500">
|
|
307
|
+
made with
|
|
308
|
+
<a
|
|
309
|
+
href="https://github.com/briancray/belte"
|
|
310
|
+
class="underline hover:text-gray-600 dark:hover:text-gray-300">
|
|
311
|
+
belte
|
|
312
|
+
</a>
|
|
313
|
+
</p>
|
|
314
|
+
</div>
|
|
315
|
+
</main>
|
|
316
|
+
{/if}
|
|
317
|
+
|
|
318
|
+
{#if showConfig}
|
|
319
|
+
<!-- First-run config modal — shown only when Start needs settings the app lacks. -->
|
|
320
|
+
<div
|
|
321
|
+
class="fixed inset-0 z-10 flex items-center justify-center bg-black/40 p-6 text-gray-900 dark:text-gray-100">
|
|
322
|
+
<div
|
|
323
|
+
class="w-full max-w-sm rounded-2xl bg-white p-8 shadow-lg ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-gray-800">
|
|
324
|
+
<h2 class="mb-5 text-lg font-semibold tracking-tight">Set up{heading}</h2>
|
|
325
|
+
|
|
326
|
+
<form
|
|
327
|
+
class="flex flex-col gap-4"
|
|
328
|
+
onsubmit={(event) => {
|
|
329
|
+
event.preventDefault()
|
|
330
|
+
void saveConfig()
|
|
331
|
+
}}>
|
|
332
|
+
{#each configFields as field (field.key)}
|
|
333
|
+
<label class="flex flex-col gap-1 text-sm">
|
|
334
|
+
<span class="font-medium">
|
|
335
|
+
{field.label}
|
|
336
|
+
{#if field.required}
|
|
337
|
+
<span class="text-red-500">*</span>
|
|
338
|
+
{/if}
|
|
339
|
+
</span>
|
|
340
|
+
{#if field.inputType === 'checkbox'}
|
|
341
|
+
<input
|
|
342
|
+
type="checkbox"
|
|
343
|
+
checked={configValues[field.key] === 'true'}
|
|
344
|
+
onchange={(event) =>
|
|
345
|
+
(configValues[field.key] = event.currentTarget.checked
|
|
346
|
+
? 'true'
|
|
347
|
+
: 'false')}
|
|
348
|
+
class="mt-1 size-4 self-start rounded border-gray-300 dark:border-gray-700">
|
|
349
|
+
{:else}
|
|
350
|
+
<input
|
|
351
|
+
type={field.inputType}
|
|
352
|
+
value={configValues[field.key] ?? ''}
|
|
353
|
+
oninput={(event) =>
|
|
354
|
+
(configValues[field.key] = event.currentTarget.value)}
|
|
355
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900 focus:ring-1 focus:ring-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:focus:border-gray-100 dark:focus:ring-gray-100">
|
|
356
|
+
{/if}
|
|
357
|
+
{#if field.description}
|
|
358
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
359
|
+
{field.description}
|
|
360
|
+
</span>
|
|
361
|
+
{/if}
|
|
362
|
+
</label>
|
|
363
|
+
{/each}
|
|
364
|
+
|
|
365
|
+
<div class="mt-1 flex gap-3">
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
onclick={() => (showConfig = false)}
|
|
369
|
+
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
|
|
370
|
+
Cancel
|
|
371
|
+
</button>
|
|
372
|
+
<button
|
|
373
|
+
type="submit"
|
|
374
|
+
disabled={!canSaveConfig || savingConfig}
|
|
375
|
+
class="flex-1 rounded-lg bg-gray-900 px-3 py-2 text-sm font-medium text-white hover:bg-gray-700 disabled:opacity-60 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-300">
|
|
376
|
+
{savingConfig ? 'Saving…' : 'Save & start'}
|
|
377
|
+
</button>
|
|
378
|
+
</div>
|
|
379
|
+
</form>
|
|
380
|
+
|
|
381
|
+
{#if error}
|
|
382
|
+
<p class="mt-4 text-center text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
383
|
+
{/if}
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { buildWebviewLib } from './buildWebviewLib.ts'
|
|
2
|
+
import { resolveWebviewLib } from './resolveWebviewLib.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Build-time guarantee that a webview library exists, returning its path.
|
|
6
|
+
Tries plain resolution first (an explicit BELTE_WEBVIEW_LIB, a bundle-local
|
|
7
|
+
copy, or a previously built cache); on a miss it compiles the vendored
|
|
8
|
+
header for the host (buildWebviewLib) and caches the result.
|
|
9
|
+
|
|
10
|
+
Used by `belte bundle` (bundleApp), which runs under bun on a developer's
|
|
11
|
+
machine — never by the compiled launcher, which only ever resolves the copy
|
|
12
|
+
shipped beside it and must not invoke a compiler on an end user's machine.
|
|
13
|
+
*/
|
|
14
|
+
export async function ensureWebviewLib(cwd: string = process.cwd()): Promise<string> {
|
|
15
|
+
try {
|
|
16
|
+
return await resolveWebviewLib(cwd)
|
|
17
|
+
} catch {
|
|
18
|
+
return await buildWebviewLib()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tie the embedded server's lifetime to the bundle launcher's.
|
|
3
|
+
|
|
4
|
+
The launcher spawns this server with BELTE_PARENT_PID set to its own pid. On a
|
|
5
|
+
clean window close the launcher reaps the child directly, but a force-quit (or
|
|
6
|
+
crash) of the launcher can't run that cleanup, which would leave the server
|
|
7
|
+
orphaned and holding its port. So when that env var is present, poll the parent
|
|
8
|
+
and exit once it's gone. A no-op when the var is absent (standalone `belte
|
|
9
|
+
start`), so it only ever activates inside a bundle.
|
|
10
|
+
*/
|
|
11
|
+
export function exitWithParent(): void {
|
|
12
|
+
const parent = process.env.BELTE_PARENT_PID
|
|
13
|
+
if (!parent) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
const parentPid = Number(parent)
|
|
17
|
+
const timer = setInterval(() => {
|
|
18
|
+
try {
|
|
19
|
+
// Signal 0 sends nothing — it only probes existence, throwing when the
|
|
20
|
+
// parent has exited (or its pid is no longer reachable).
|
|
21
|
+
process.kill(parentPid, 0)
|
|
22
|
+
} catch {
|
|
23
|
+
process.exit(0)
|
|
24
|
+
}
|
|
25
|
+
}, 1000)
|
|
26
|
+
// The watchdog alone shouldn't keep the server process alive.
|
|
27
|
+
timer.unref()
|
|
28
|
+
}
|