@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,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Wire format for a single cached response shipped from SSR to client hydration.
|
|
3
|
+
Only GET/DELETE entries with a textual Content-Type are emitted — POST/PUT
|
|
4
|
+
bodies can't be reconstructed without shipping the original request body,
|
|
5
|
+
and binary bodies don't survive a JSON round-trip.
|
|
6
|
+
*/
|
|
7
|
+
export type CacheSnapshotEntry = {
|
|
8
|
+
key: string
|
|
9
|
+
url: string
|
|
10
|
+
method: 'GET' | 'DELETE'
|
|
11
|
+
status: number
|
|
12
|
+
statusText: string
|
|
13
|
+
headers: Array<[string, string]>
|
|
14
|
+
body: string
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CacheEntry } from './CacheEntry.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Cache map paired with a Svelte-aware per-key subscriber. Calling
|
|
5
|
+
`subscribe(key)` from inside a tracking scope ($derived / $effect) registers
|
|
6
|
+
that scope to re-run when the entry is invalidated; called outside tracking
|
|
7
|
+
it's a no-op. Subscribers live for the lifetime of the store: the server
|
|
8
|
+
uses a fresh store per request (so subscribers die with the response), the
|
|
9
|
+
client uses a single module-level store (so subscribers persist for the tab).
|
|
10
|
+
|
|
11
|
+
`trackLifecycle` is the store-wide counterpart used by cache.pending: unlike a
|
|
12
|
+
keyed read it matches many entries (or all), so it re-derives by scanning
|
|
13
|
+
entries and only needs one "in-flight membership changed" signal. Reading it in
|
|
14
|
+
a tracking scope re-runs that scope whenever any call starts, settles, or is
|
|
15
|
+
evicted.
|
|
16
|
+
*/
|
|
17
|
+
export type CacheStore = {
|
|
18
|
+
entries: Map<string, CacheEntry>
|
|
19
|
+
events: EventTarget
|
|
20
|
+
subscribe: (key: string) => void
|
|
21
|
+
trackLifecycle: () => void
|
|
22
|
+
/*
|
|
23
|
+
Keys dropped by a (policy-less) invalidate, awaiting their next read. The
|
|
24
|
+
drop erases the entry, so the next cache() call is a cold miss with no memory
|
|
25
|
+
it followed an invalidate; this set carries that signal across the gap so the
|
|
26
|
+
replacement entry is flagged a reload (cache.refreshing true) rather than a
|
|
27
|
+
first-ever load. Consumed when that entry is created; a key invalidated but
|
|
28
|
+
never re-read just lingers (bounded by distinct such keys; the server's
|
|
29
|
+
request-scoped store discards it with the response).
|
|
30
|
+
*/
|
|
31
|
+
pendingRefresh: Set<string>
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Which client surfaces a verb or socket is exposed to. Browser is the
|
|
3
|
+
historical default; MCP and CLI flip on automatically when the
|
|
4
|
+
declaration carries a Standard Schema (the schema is what makes the
|
|
5
|
+
non-browser surfaces safe to advertise). Explicit values always win.
|
|
6
|
+
*/
|
|
7
|
+
export type ClientFlags = {
|
|
8
|
+
browser: boolean
|
|
9
|
+
mcp: boolean
|
|
10
|
+
cli: boolean
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type HttpVerb = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The launcher/CLI-owned record of the last connection, kept in the data dir so it
|
|
3
|
+
survives relaunch and is readable before any window or session opens. It records
|
|
4
|
+
the *intent*, not a concrete embedded URL — an embedded server picks a fresh port
|
|
5
|
+
each launch, so only `{ kind: 'embedded' }` is durable; a remote connection keeps
|
|
6
|
+
its url. resolveLaunchTarget / resolveCliTarget read it; /connect and /start write
|
|
7
|
+
it; /disconnect clears it.
|
|
8
|
+
*/
|
|
9
|
+
export type LastConnection = { kind: 'embedded' } | { kind: 'url'; url: string }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A single declared argument of a markdown prompt, parsed from the file's
|
|
3
|
+
YAML frontmatter `arguments:` list. `name` is the placeholder the body
|
|
4
|
+
interpolates via `{{name}}`; `description` + `required` feed the argument
|
|
5
|
+
list MCP advertises in `prompts/list`. Build-time only — markdown prompts
|
|
6
|
+
carry no runtime schema object, so this drives the generated JSON Schema.
|
|
7
|
+
*/
|
|
8
|
+
export type PromptArgument = {
|
|
9
|
+
name: string
|
|
10
|
+
description?: string
|
|
11
|
+
required?: boolean
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HttpVerb } from './HttpVerb.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Bare-response remote function — same call shape as RemoteFunction but
|
|
5
|
+
resolves to the underlying Response without Content-Type decode and
|
|
6
|
+
without throwing on non-2xx. Produced as `.raw` on every RemoteFunction
|
|
7
|
+
so callers that need status / headers / body streaming or want to
|
|
8
|
+
implement custom error handling can opt out of the decode.
|
|
9
|
+
*/
|
|
10
|
+
export type RawRemoteFunction<Args> = ((args: Args | FormData) => Promise<Response>) & {
|
|
11
|
+
readonly method: HttpVerb
|
|
12
|
+
readonly url: string
|
|
13
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ClientFlags } from './ClientFlags.ts'
|
|
2
|
+
import type { HttpVerb } from './HttpVerb.ts'
|
|
3
|
+
import type { RawRemoteFunction } from './RawRemoteFunction.ts'
|
|
4
|
+
import type { Subscribable } from './Subscribable.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Remote function reference produced by GET/POST/... inside an `$rpc/**`
|
|
8
|
+
module and consumed by rpc dispatch, cache(), SSR auto-hydration, and
|
|
9
|
+
direct calls. Same callable signature on server and client — the bundler
|
|
10
|
+
swaps the implementation for browser builds.
|
|
11
|
+
|
|
12
|
+
The plain call resolves to the decoded body shape (sniffed from
|
|
13
|
+
Content-Type) and throws HttpError on non-2xx. `.raw` is a sibling
|
|
14
|
+
RemoteFunction whose call resolves to the underlying Response — same
|
|
15
|
+
method, same url, same args, no decode. Pass `fn.raw` to cache() to
|
|
16
|
+
memoise raw Responses against the same cache key as `fn` (both share one
|
|
17
|
+
stored entry — the decode just happens on the way out for callers of
|
|
18
|
+
`fn`). `.stream(args)` returns an iterable view of the Response body:
|
|
19
|
+
SSE / JSONL handlers yield each frame; non-streaming handlers yield the
|
|
20
|
+
decoded body once then complete. The result is a Subscribable, so it
|
|
21
|
+
can be passed to subscribe() and shared across reactive consumers.
|
|
22
|
+
For sustained broadcast / pub-sub use the `belte/server/socket` primitive —
|
|
23
|
+
HTTP rpc isn't the place for long-lived multi-publisher subscriptions.
|
|
24
|
+
`.fetch(req)` is the framework's request-dispatch entry point — used by
|
|
25
|
+
the router to invoke the handler from an incoming HTTP request, not
|
|
26
|
+
for user code.
|
|
27
|
+
*/
|
|
28
|
+
/*
|
|
29
|
+
A body verb (POST/PUT/PATCH) also accepts a FormData in place of typed args:
|
|
30
|
+
buildRpcRequest ships it as a multipart body and the server splits text fields
|
|
31
|
+
into args (still schema-validated) and File parts into files(). FormData is
|
|
32
|
+
stringly-typed, so this is the upload escape hatch — typed object args remain
|
|
33
|
+
the default for everything else.
|
|
34
|
+
*/
|
|
35
|
+
export type RemoteFunction<Args, Return> = ((args: Args | FormData) => Promise<Return>) & {
|
|
36
|
+
readonly method: HttpVerb
|
|
37
|
+
readonly url: string
|
|
38
|
+
readonly clients: ClientFlags
|
|
39
|
+
readonly raw: RawRemoteFunction<Args>
|
|
40
|
+
stream(args?: Args | FormData): Subscribable<Return>
|
|
41
|
+
fetch(request: Request): Promise<Response>
|
|
42
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Mirror of the Standard Schema v1 spec interface (standardschema.dev). Any
|
|
3
|
+
library that implements the spec — zod, valibot, arktype, etc. — produces
|
|
4
|
+
values whose `~standard` property structurally matches this shape, so users
|
|
5
|
+
can pass their existing schemas to verb helpers without an adapter.
|
|
6
|
+
|
|
7
|
+
Kept inline (no `@standard-schema/spec` dep) because the spec is type-only
|
|
8
|
+
and tiny; adding a package for ~30 lines of interface would be churn. The
|
|
9
|
+
namespace pattern below is the spec's own convention — `InferInput` /
|
|
10
|
+
`InferOutput` ride along the same export.
|
|
11
|
+
*/
|
|
12
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
13
|
+
readonly '~standard': StandardSchemaV1.Props<Input, Output>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// biome-ignore lint/style/useNamingConvention: matches the Standard Schema spec exactly
|
|
17
|
+
export namespace StandardSchemaV1 {
|
|
18
|
+
export interface Props<Input = unknown, Output = Input> {
|
|
19
|
+
readonly version: 1
|
|
20
|
+
readonly vendor: string
|
|
21
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>
|
|
22
|
+
readonly types?: Types<Input, Output> | undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Result<Output> = SuccessResult<Output> | FailureResult
|
|
26
|
+
|
|
27
|
+
export interface SuccessResult<Output> {
|
|
28
|
+
readonly value: Output
|
|
29
|
+
readonly issues?: undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FailureResult {
|
|
33
|
+
readonly issues: ReadonlyArray<Issue>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Issue {
|
|
37
|
+
readonly message: string
|
|
38
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PathSegment {
|
|
42
|
+
readonly key: PropertyKey
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Types<Input = unknown, Output = Input> {
|
|
46
|
+
readonly input: Input
|
|
47
|
+
readonly output: Output
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type InferInput<Schema extends StandardSchemaV1> = NonNullable<
|
|
51
|
+
Schema['~standard']['types']
|
|
52
|
+
>['input']
|
|
53
|
+
|
|
54
|
+
export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
|
|
55
|
+
Schema['~standard']['types']
|
|
56
|
+
>['output']
|
|
57
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CacheSnapshotEntry } from './CacheSnapshotEntry.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Payload of one streamed `window.__belteResolve(...)` call. A full
|
|
5
|
+
`CacheSnapshotEntry` settles the placeholder with warm data; a `{ key, miss }`
|
|
6
|
+
marker means the server couldn't snapshot that body (binary, rejected, evicted)
|
|
7
|
+
so the client settles the placeholder with a live re-fetch instead. Discriminate
|
|
8
|
+
on the `miss` field.
|
|
9
|
+
*/
|
|
10
|
+
export type StreamedResolution = CacheSnapshotEntry | { key: string; miss: true }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Wire descriptor for a {#await} cache read that's still pending at first flush.
|
|
3
|
+
Shipped in `__SSR__.streaming` so the client can pre-create a deferred cache
|
|
4
|
+
entry for the key before hydration — `cache()` then hits that placeholder
|
|
5
|
+
instead of firing its own fetch, and the streamed `__belteResolve` settles it.
|
|
6
|
+
`url`/`method` reconstruct the entry's Request for the rare miss fallback (a
|
|
7
|
+
non-snapshottable body the client re-fetches live).
|
|
8
|
+
*/
|
|
9
|
+
export type StreamingPlaceholder = {
|
|
10
|
+
key: string
|
|
11
|
+
url: string
|
|
12
|
+
method: string
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The thing `subscribe()` reads from: an AsyncIterable carrying a stable
|
|
3
|
+
`name` used as the subscription registry key. Both `Socket<T>` (the
|
|
4
|
+
declared broadcast primitive) and the result of `fn.stream(args)`
|
|
5
|
+
(per-call HTTP stream consumer) satisfy this shape, so subscribe() can
|
|
6
|
+
share one iterator across multiple readers regardless of source.
|
|
7
|
+
|
|
8
|
+
The name on a Socket comes from the file path under `src/server/sockets/`.
|
|
9
|
+
The name on an fn.stream(args) result is `keyForRemoteCall(method, url,
|
|
10
|
+
args)` — the same key cache() uses — so two subscribers to the same
|
|
11
|
+
remote-call args dedupe to one underlying fetch.
|
|
12
|
+
*/
|
|
13
|
+
export interface Subscribable<T> extends AsyncIterable<T> {
|
|
14
|
+
readonly name: string
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from './types/StandardSchemaV1.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Attaches a `toJSONSchema()` projection to a Standard Schema whose library
|
|
5
|
+
doesn't expose one natively. jsonSchemaForSchema probes that method to feed the
|
|
6
|
+
OpenAPI document, the MCP tool schemas, the CLI flag help, and the bundle setup
|
|
7
|
+
form. Zod 4 / Effect / Arktype carry their own; everything else wraps once where
|
|
8
|
+
the schema is declared:
|
|
9
|
+
|
|
10
|
+
export const config = env(withJsonSchema(vSchema, (s) => toJsonSchema(s)))
|
|
11
|
+
|
|
12
|
+
Mutates and returns the same schema with the method attached, so the wrapped
|
|
13
|
+
value stays usable everywhere the bare schema was.
|
|
14
|
+
*/
|
|
15
|
+
export function withJsonSchema<Schema extends StandardSchemaV1>(
|
|
16
|
+
schema: Schema,
|
|
17
|
+
toJsonSchema: (schema: Schema) => Record<string, unknown>,
|
|
18
|
+
): Schema & { toJSONSchema: () => Record<string, unknown> } {
|
|
19
|
+
return Object.assign(schema, { toJSONSchema: () => toJsonSchema(schema) })
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises'
|
|
2
|
+
import { appDataDir } from './appDataDir.ts'
|
|
3
|
+
import { lastConnectionPath } from './lastConnectionPath.ts'
|
|
4
|
+
import type { LastConnection } from './types/LastConnection.ts'
|
|
5
|
+
|
|
6
|
+
// Persists the connection intent, creating the data dir on first write.
|
|
7
|
+
export async function writeLastConnection(
|
|
8
|
+
programName: string,
|
|
9
|
+
value: LastConnection,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
await mkdir(appDataDir(programName), { recursive: true })
|
|
12
|
+
await Bun.write(lastConnectionPath(programName), JSON.stringify(value))
|
|
13
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { pageUrlForFile } from './pageUrlForFile.ts'
|
|
2
|
+
import { parseRouteSegments } from './parseRouteSegments.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Walks a `[name]` / `[...rest]` route URL and returns the param shape it
|
|
6
|
+
declares. Catch-all segments map to `string` under their declared name —
|
|
7
|
+
the server's toBunRoutePattern renames Bun's `*` key back to that name
|
|
8
|
+
when dispatching, so the page component sees `params.rest`, not
|
|
9
|
+
`params['*']`.
|
|
10
|
+
*/
|
|
11
|
+
function paramsForRoute(routeUrl: string): Record<string, 'string'> {
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
parseRouteSegments(routeUrl)
|
|
14
|
+
.filter((segment) => segment.kind === 'param')
|
|
15
|
+
.map((segment) => [segment.name, 'string'] as const),
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderParamsShape(shape: Record<string, 'string'>): string {
|
|
20
|
+
const keys = Object.keys(shape)
|
|
21
|
+
if (keys.length === 0) {
|
|
22
|
+
return 'Record<string, never>'
|
|
23
|
+
}
|
|
24
|
+
return `{ ${keys.map((key) => `${JSON.stringify(key)}: string`).join('; ')} }`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
Emits a `.d.ts` that augments belte's `Routes` interface with one entry per
|
|
29
|
+
page file in the project. Page picks this up as a discriminated union keyed
|
|
30
|
+
on `route`, so `if (page.route === '/media/[id]') page.params.id` is typed
|
|
31
|
+
automatically without consumers writing route types by hand.
|
|
32
|
+
The file is written to `src/.belte/routes.d.ts` so the consumer's existing
|
|
33
|
+
src tsconfig include picks it up with no extra configuration. The augmented
|
|
34
|
+
module is keyed on the name the project imports belte under (`importName`),
|
|
35
|
+
so the augmentation matches the consumer's `page` import whether belte is
|
|
36
|
+
installed directly (`@belte/belte`) or behind an alias.
|
|
37
|
+
*/
|
|
38
|
+
export async function writeRoutesDts({
|
|
39
|
+
cwd,
|
|
40
|
+
pageFiles,
|
|
41
|
+
importName,
|
|
42
|
+
}: {
|
|
43
|
+
cwd: string
|
|
44
|
+
pageFiles: string[]
|
|
45
|
+
importName: string
|
|
46
|
+
}): Promise<void> {
|
|
47
|
+
const entries = pageFiles
|
|
48
|
+
.map((file) => ({
|
|
49
|
+
route: pageUrlForFile(file),
|
|
50
|
+
params: paramsForRoute(pageUrlForFile(file)),
|
|
51
|
+
}))
|
|
52
|
+
.toSorted((a, b) => a.route.localeCompare(b.route))
|
|
53
|
+
.map(
|
|
54
|
+
({ route, params }) => ` ${JSON.stringify(route)}: ${renderParamsShape(params)}`,
|
|
55
|
+
)
|
|
56
|
+
.join('\n')
|
|
57
|
+
const contents = `// Generated by belte. Do not edit by hand.
|
|
58
|
+
declare module '${importName}/browser/page' {
|
|
59
|
+
interface Routes {
|
|
60
|
+
${entries}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {}
|
|
65
|
+
`
|
|
66
|
+
await Bun.write(`${cwd}/src/.belte/routes.d.ts`, contents)
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { verbRegistry } from '../server/rpc/verbRegistry.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Empties the process-wide verb registry. defineVerb registers on construction
|
|
5
|
+
and never removes, so verbs declared in one test stay discoverable by name in
|
|
6
|
+
the next. Call in beforeEach to isolate a suite that defines verbs inline,
|
|
7
|
+
keeping createTestClient's name lookup unambiguous across tests.
|
|
8
|
+
*/
|
|
9
|
+
export function clearVerbRegistry(): void {
|
|
10
|
+
verbRegistry.clear()
|
|
11
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { AppModule } from '../server/AppModule.ts'
|
|
2
|
+
import { dispatchVerbInProcess } from '../server/rpc/dispatchVerbInProcess.ts'
|
|
3
|
+
import { findVerbByCommandName } from '../server/rpc/findVerbByCommandName.ts'
|
|
4
|
+
import type { VerbRegistryEntry } from '../server/rpc/types/VerbRegistryEntry.ts'
|
|
5
|
+
import { decodeResponse } from '../shared/decodeResponse.ts'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
Each property is a callable: invoking it decodes the body (and throws
|
|
9
|
+
HttpError on non-2xx, like a real remote call), while `.raw(args)` returns
|
|
10
|
+
the underlying Response untouched for status/header/streaming assertions.
|
|
11
|
+
*/
|
|
12
|
+
type TestInvoker = ((args?: unknown) => Promise<unknown>) & {
|
|
13
|
+
raw: (args?: unknown) => Promise<Response>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type AnyApi = Record<string, TestInvoker>
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
In-process client for tests. Like createClient's in-process mode it discovers
|
|
20
|
+
verbs from the registry (populated by the test's defineVerb calls) and routes
|
|
21
|
+
through dispatchVerbInProcess — same synthesize-and-fetch the CLI and MCP
|
|
22
|
+
surfaces use, so they can't drift on how a verb is invoked. Each call runs
|
|
23
|
+
inside runWithRequestScope, the same seam createServer crosses for a live
|
|
24
|
+
request, so request-scoped helpers behave identically to production — a fresh
|
|
25
|
+
per-request cache(), the cookie jar with Set-Cookie flush, request()/server()
|
|
26
|
+
resolution, and handleError/500 fallback on a throw.
|
|
27
|
+
|
|
28
|
+
`headers` pre-populates the synthetic Request (auth, cookies) for handlers that
|
|
29
|
+
read inbound identity; `app.handleError` lets a suite assert its custom error
|
|
30
|
+
response. No `url` — this never hits the network, so it needs the rpc modules
|
|
31
|
+
imported into the process, not a running server.
|
|
32
|
+
*/
|
|
33
|
+
export function createTestClient<Api extends AnyApi = AnyApi>(opts?: {
|
|
34
|
+
baseUrl?: string
|
|
35
|
+
headers?: HeadersInit
|
|
36
|
+
app?: AppModule
|
|
37
|
+
}): Api {
|
|
38
|
+
const baseUrl = opts?.baseUrl ?? 'http://localhost/'
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
Fresh Headers per call: buildRpcRequest mutates it (sets content-type on
|
|
42
|
+
body verbs), so a shared instance would leak that mutation across calls.
|
|
43
|
+
*/
|
|
44
|
+
function send(entry: VerbRegistryEntry, args: unknown): Promise<Response> {
|
|
45
|
+
return dispatchVerbInProcess({
|
|
46
|
+
entry,
|
|
47
|
+
args,
|
|
48
|
+
baseUrl,
|
|
49
|
+
headers: new Headers(opts?.headers),
|
|
50
|
+
app: opts?.app,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildInvoker(entry: VerbRegistryEntry): TestInvoker {
|
|
55
|
+
const invoker = (async (args?: unknown) =>
|
|
56
|
+
decodeResponse(await send(entry, args))) as TestInvoker
|
|
57
|
+
invoker.raw = (args?: unknown) => send(entry, args)
|
|
58
|
+
return invoker
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Memoise per-name so repeated accesses skip the registry scan + closure alloc.
|
|
62
|
+
const invokerCache = new Map<string, TestInvoker | undefined>()
|
|
63
|
+
|
|
64
|
+
return new Proxy({} as Api, {
|
|
65
|
+
get(_target, prop): TestInvoker | undefined {
|
|
66
|
+
if (typeof prop !== 'string') {
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
if (invokerCache.has(prop)) {
|
|
70
|
+
return invokerCache.get(prop)
|
|
71
|
+
}
|
|
72
|
+
const entry = findVerbByCommandName(prop)
|
|
73
|
+
const invoker = entry ? buildInvoker(entry) : undefined
|
|
74
|
+
invokerCache.set(prop, invoker)
|
|
75
|
+
return invoker
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
package/src/preload.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { plugin } from 'bun'
|
|
2
|
+
import { belteResolverPlugin } from './belteResolverPlugin.ts'
|
|
3
|
+
import { loadSvelteConfig } from './lib/shared/loadSvelteConfig.ts'
|
|
4
|
+
import { sveltePlugin } from './sveltePlugin.ts'
|
|
5
|
+
|
|
6
|
+
const mode = (process.env.BELTE_SVELTE_MODE ?? 'server') as 'server' | 'client'
|
|
7
|
+
const svelteConfig = await loadSvelteConfig()
|
|
8
|
+
|
|
9
|
+
await plugin(sveltePlugin({ generate: mode, svelteConfig }))
|
|
10
|
+
await plugin(belteResolverPlugin({ target: mode }))
|
|
11
|
+
|
|
12
|
+
await plugin({
|
|
13
|
+
name: 'css-noop',
|
|
14
|
+
setup(build) {
|
|
15
|
+
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
16
|
+
contents: 'export default {};',
|
|
17
|
+
loader: 'js',
|
|
18
|
+
}))
|
|
19
|
+
},
|
|
20
|
+
})
|
package/src/scaffold.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Glob } from 'bun'
|
|
2
|
+
import { log } from './lib/shared/log.ts'
|
|
3
|
+
|
|
4
|
+
const TEMPLATE_DIR = new URL('../template', import.meta.url).pathname
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Copies the bundled template directory into `${cwd}/${name}`. Refuses to write
|
|
8
|
+
into a non-empty directory so an accidental run doesn't overwrite real work.
|
|
9
|
+
*/
|
|
10
|
+
export async function scaffold({
|
|
11
|
+
cwd = process.cwd(),
|
|
12
|
+
name,
|
|
13
|
+
}: {
|
|
14
|
+
cwd?: string
|
|
15
|
+
name: string
|
|
16
|
+
}): Promise<string> {
|
|
17
|
+
const trimmed = name.trim()
|
|
18
|
+
if (trimmed === '') {
|
|
19
|
+
throw new Error('[belte] project name is required: bunx belte scaffold <name>')
|
|
20
|
+
}
|
|
21
|
+
const target = resolveTarget(cwd, trimmed)
|
|
22
|
+
if (await targetIsNonEmpty(target)) {
|
|
23
|
+
throw new Error(`[belte] target directory is not empty: ${target}`)
|
|
24
|
+
}
|
|
25
|
+
if (!(await Bun.file(`${TEMPLATE_DIR}/package.json`).exists())) {
|
|
26
|
+
throw new Error(`[belte] template missing at ${TEMPLATE_DIR}`)
|
|
27
|
+
}
|
|
28
|
+
await copyTree(TEMPLATE_DIR, target)
|
|
29
|
+
log.success(`scaffolded belte project at ${target}`)
|
|
30
|
+
log.detail(' next steps:')
|
|
31
|
+
if (target !== cwd) {
|
|
32
|
+
log.detail(` cd ${trimmed}`)
|
|
33
|
+
}
|
|
34
|
+
log.detail(' bun install')
|
|
35
|
+
log.detail(' bun run dev')
|
|
36
|
+
return target
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
Copies every file under `from` into `to`, preserving relative paths. Uses
|
|
41
|
+
Bun.Glob to enumerate (dotfiles included) and Bun.write to materialize each
|
|
42
|
+
file — Bun.write auto-creates parent directories.
|
|
43
|
+
*/
|
|
44
|
+
async function copyTree(from: string, to: string): Promise<void> {
|
|
45
|
+
const files = await Array.fromAsync(
|
|
46
|
+
new Glob('**/*').scan({ cwd: from, onlyFiles: true, dot: true }),
|
|
47
|
+
)
|
|
48
|
+
await Promise.all(
|
|
49
|
+
files.map(async (relativePath) => {
|
|
50
|
+
const source = Bun.file(`${from}/${relativePath}`)
|
|
51
|
+
await Bun.write(`${to}/${relativePath}`, source)
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
Resolves the user-supplied name against the working directory. Absolute
|
|
58
|
+
paths (`/tmp/foo`) and `~`-prefixed paths are used as-is; relative names
|
|
59
|
+
are joined onto `cwd`.
|
|
60
|
+
*/
|
|
61
|
+
function resolveTarget(cwd: string, name: string): string {
|
|
62
|
+
if (name === '.' || name === './') {
|
|
63
|
+
return cwd
|
|
64
|
+
}
|
|
65
|
+
if (name.startsWith('/')) {
|
|
66
|
+
return name
|
|
67
|
+
}
|
|
68
|
+
if (name.startsWith('~/')) {
|
|
69
|
+
const home = process.env.HOME ?? ''
|
|
70
|
+
return `${home}${name.slice(1)}`
|
|
71
|
+
}
|
|
72
|
+
return `${cwd}/${name}`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
True when the target exists and contains at least one entry. Uses Bun.Glob
|
|
77
|
+
rather than fs.readdir to honor the project's "Bun-first" rule. A missing
|
|
78
|
+
directory is reported as empty so first-time scaffolds proceed.
|
|
79
|
+
*/
|
|
80
|
+
async function targetIsNonEmpty(target: string): Promise<boolean> {
|
|
81
|
+
try {
|
|
82
|
+
for await (const _ of new Glob('*').scan({ cwd: target, onlyFiles: false, dot: true })) {
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
throw error
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BunPlugin } from 'bun'
|
|
2
|
+
import { belteResolverPlugin } from './belteResolverPlugin.ts'
|
|
3
|
+
import type { SvelteConfig } from './lib/shared/types/SvelteConfig.ts'
|
|
4
|
+
import { sveltePlugin } from './sveltePlugin.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
The server-target Bun.build plugin pair shared by compile / buildCli /
|
|
8
|
+
bundleApp: the svelte loader (server generate) plus belte's virtual-module
|
|
9
|
+
resolver. `embedAssets` flips on the zstd asset embed used by the standalone
|
|
10
|
+
server binary; the CLI + launcher builds leave it off.
|
|
11
|
+
*/
|
|
12
|
+
export function serverBuildPlugins({
|
|
13
|
+
cwd,
|
|
14
|
+
svelteConfig,
|
|
15
|
+
embedAssets = false,
|
|
16
|
+
}: {
|
|
17
|
+
cwd: string
|
|
18
|
+
svelteConfig?: SvelteConfig
|
|
19
|
+
embedAssets?: boolean
|
|
20
|
+
}): BunPlugin[] {
|
|
21
|
+
return [
|
|
22
|
+
sveltePlugin({ generate: 'server', svelteConfig }),
|
|
23
|
+
belteResolverPlugin({ cwd, embedAssets, target: 'server' }),
|
|
24
|
+
]
|
|
25
|
+
}
|