@abide/abide 0.28.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 +607 -0
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/bin/abide.ts +212 -0
- package/package.json +155 -0
- package/src/abideLsp.ts +211 -0
- package/src/abideModules.d.ts +8 -0
- package/src/abideResolverPlugin.ts +923 -0
- package/src/appEntry.ts +151 -0
- package/src/assets/app.html +12 -0
- package/src/build.ts +143 -0
- package/src/buildCli.ts +127 -0
- package/src/buildDisconnected.ts +118 -0
- package/src/bundleApp.ts +147 -0
- package/src/bundleDisconnectedEntry.ts +14 -0
- package/src/checkAbide.ts +77 -0
- package/src/cliEntry.ts +25 -0
- package/src/clientBuildPlugins.ts +33 -0
- package/src/clientEntry.ts +17 -0
- package/src/compile.ts +63 -0
- package/src/controlServerWorker.ts +426 -0
- package/src/devEntry.ts +250 -0
- package/src/discoveryEntry.ts +81 -0
- package/src/lib/bundle/BundleMenu.ts +12 -0
- package/src/lib/bundle/BundleMenuItem.ts +25 -0
- package/src/lib/bundle/BundleWindow.ts +37 -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 +34 -0
- package/src/lib/bundle/buildWebviewLib.ts +111 -0
- package/src/lib/bundle/bundled.ts +35 -0
- package/src/lib/bundle/disconnected.abide +236 -0
- package/src/lib/bundle/disconnected.css +9 -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/abideMenu.mm +422 -0
- package/src/lib/bundle/native/webview.h +4557 -0
- package/src/lib/bundle/onMenu.ts +42 -0
- package/src/lib/bundle/openWebview.ts +104 -0
- package/src/lib/bundle/pngToIcns.ts +47 -0
- package/src/lib/bundle/probeAbideServer.ts +57 -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 +37 -0
- package/src/lib/bundle/spawnEmbeddedServer.ts +64 -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 +108 -0
- package/src/lib/cli/dispatchCommand.ts +71 -0
- package/src/lib/cli/loadEnvFromBinaryDir.ts +15 -0
- package/src/lib/cli/parseArgvForRpc.ts +100 -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 +176 -0
- package/src/lib/cli/runSession.ts +108 -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 +102 -0
- package/src/lib/mcp/createMcpServer.ts +48 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +138 -0
- package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
- package/src/lib/mcp/mcpSurface.ts +295 -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 +47 -0
- package/src/lib/server/DELETE.ts +10 -0
- package/src/lib/server/GET.ts +10 -0
- package/src/lib/server/HEAD.ts +10 -0
- package/src/lib/server/PATCH.ts +10 -0
- package/src/lib/server/POST.ts +10 -0
- package/src/lib/server/PUT.ts +10 -0
- package/src/lib/server/agent.ts +86 -0
- package/src/lib/server/appDataDir.ts +16 -0
- package/src/lib/server/cli/buildEnvContent.ts +19 -0
- package/src/lib/server/cli/createTarGz.ts +77 -0
- package/src/lib/server/cli/handleCliDownload.ts +150 -0
- package/src/lib/server/cli/handleCliInstall.ts +37 -0
- package/src/lib/server/cli/installScript.ts +31 -0
- package/src/lib/server/cli/maxSourceMtime.ts +26 -0
- package/src/lib/server/cookies.ts +30 -0
- package/src/lib/server/env.ts +51 -0
- package/src/lib/server/error.ts +73 -0
- package/src/lib/server/json.ts +42 -0
- package/src/lib/server/jsonl.ts +47 -0
- package/src/lib/server/prompts/definePrompt.ts +21 -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 +17 -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/reachable.ts +45 -0
- package/src/lib/server/redirect.ts +43 -0
- package/src/lib/server/request.ts +19 -0
- package/src/lib/server/rpc/defineVerb.ts +210 -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 +127 -0
- package/src/lib/server/rpc/readBodyWithinLimit.ts +44 -0
- package/src/lib/server/rpc/registerVerb.ts +6 -0
- package/src/lib/server/rpc/runWithVerbTimeout.ts +49 -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 +87 -0
- package/src/lib/server/rpc/types/VerbRegistryEntry.ts +35 -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_READY_MESSAGE.ts +6 -0
- package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +107 -0
- package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +16 -0
- package/src/lib/server/runtime/acceptsGzip.ts +24 -0
- package/src/lib/server/runtime/buildCacheSnapshot.ts +61 -0
- package/src/lib/server/runtime/buildHealthPayload.ts +34 -0
- package/src/lib/server/runtime/buildInspectorSurface.ts +37 -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/createAppAssetServer.ts +76 -0
- package/src/lib/server/runtime/createAssetHeaderCache.ts +31 -0
- package/src/lib/server/runtime/createPublicAssetServer.ts +67 -0
- package/src/lib/server/runtime/createReachable.ts +109 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +127 -0
- package/src/lib/server/runtime/createServer.ts +674 -0
- package/src/lib/server/runtime/createUiPageRenderer.ts +181 -0
- package/src/lib/server/runtime/crossOriginForbidden.ts +17 -0
- package/src/lib/server/runtime/crossOriginGate.ts +29 -0
- package/src/lib/server/runtime/devClientFingerprint.ts +117 -0
- package/src/lib/server/runtime/devHotModuleResponse.ts +40 -0
- package/src/lib/server/runtime/devReloadResponse.ts +41 -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 +21 -0
- package/src/lib/server/runtime/getActiveServer.ts +6 -0
- package/src/lib/server/runtime/globToPathSet.ts +29 -0
- package/src/lib/server/runtime/gzipResponse.ts +46 -0
- package/src/lib/server/runtime/inProcessServer.ts +20 -0
- package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
- package/src/lib/server/runtime/isCrossOriginRequest.ts +23 -0
- package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
- package/src/lib/server/runtime/logExposedSurfaces.ts +156 -0
- package/src/lib/server/runtime/maybeMountInspector.ts +97 -0
- package/src/lib/server/runtime/mimeForExtension.ts +14 -0
- package/src/lib/server/runtime/pageUrlFromStore.ts +15 -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/resolvePageSnapshot.ts +25 -0
- package/src/lib/server/runtime/respondWithEmbeddedAsset.ts +18 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +150 -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 +83 -0
- package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
- package/src/lib/server/runtime/streamFromIterator.ts +86 -0
- package/src/lib/server/runtime/types/Assets.ts +6 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +18 -0
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +24 -0
- package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +11 -0
- package/src/lib/server/runtime/types/InspectorContext.ts +30 -0
- package/src/lib/server/runtime/types/InspectorSocket.ts +17 -0
- package/src/lib/server/runtime/types/InspectorSurface.ts +13 -0
- package/src/lib/server/runtime/types/InspectorVerb.ts +27 -0
- package/src/lib/server/runtime/types/RequestStore.ts +55 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +32 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
- package/src/lib/server/server.ts +33 -0
- package/src/lib/server/socket.ts +32 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +337 -0
- package/src/lib/server/sockets/defineSocket.ts +179 -0
- package/src/lib/server/sockets/lookupSocket.ts +6 -0
- package/src/lib/server/sockets/registerSocket.ts +6 -0
- package/src/lib/server/sockets/socketOperations.ts +36 -0
- package/src/lib/server/sockets/socketRegistry.ts +9 -0
- package/src/lib/server/sockets/types/Socket.ts +23 -0
- package/src/lib/server/sockets/types/SocketClientFrame.ts +19 -0
- package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
- package/src/lib/server/sockets/types/SocketOptions.ts +26 -0
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +19 -0
- package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
- package/src/lib/server/sockets/types/SocketServerFrame.ts +24 -0
- package/src/lib/server/sse.ts +54 -0
- package/src/lib/shared/ABIDE_PACKAGE_NAME.ts +7 -0
- package/src/lib/shared/ABIDE_VERSION.ts +9 -0
- package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
- package/src/lib/shared/CACHE_WRAPPED.ts +8 -0
- package/src/lib/shared/CLI_PATH.ts +7 -0
- package/src/lib/shared/DEV_HOT_PREFIX.ts +7 -0
- package/src/lib/shared/DEV_RELOAD_PATH.ts +6 -0
- package/src/lib/shared/HEALTH_PATH.ts +7 -0
- package/src/lib/shared/HttpError.ts +20 -0
- package/src/lib/shared/IDENTITY_PATH.ts +6 -0
- package/src/lib/shared/INSPECTOR_PATH.ts +7 -0
- package/src/lib/shared/NAV_HEADER.ts +8 -0
- package/src/lib/shared/OFFLINE_HEADER.ts +8 -0
- package/src/lib/shared/REMOTE_FUNCTION.ts +8 -0
- package/src/lib/shared/REPLAYABLE_METHODS.ts +12 -0
- package/src/lib/shared/SOCKETS_PATH.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
- package/src/lib/shared/SocketDisconnectedError.ts +13 -0
- package/src/lib/shared/TEXT_PLAIN.ts +7 -0
- package/src/lib/shared/abideImportName.ts +44 -0
- package/src/lib/shared/abideLog.ts +38 -0
- package/src/lib/shared/activeCacheStore.ts +20 -0
- package/src/lib/shared/activePage.ts +25 -0
- package/src/lib/shared/appDataDir.ts +34 -0
- package/src/lib/shared/appNameSlot.ts +10 -0
- package/src/lib/shared/basePath.ts +10 -0
- package/src/lib/shared/basePathFromAppUrl.ts +20 -0
- package/src/lib/shared/baseSlot.ts +14 -0
- package/src/lib/shared/binaryDirEnvPath.ts +12 -0
- package/src/lib/shared/browserClientFlags.ts +10 -0
- package/src/lib/shared/buildRpcProxy.ts +39 -0
- package/src/lib/shared/buildRpcRequest.ts +70 -0
- package/src/lib/shared/buildSocketOverChannel.ts +58 -0
- package/src/lib/shared/bundleLayout.ts +36 -0
- package/src/lib/shared/cache.ts +951 -0
- package/src/lib/shared/cacheEntryFromSnapshot.ts +59 -0
- package/src/lib/shared/cacheStoreSlot.ts +16 -0
- package/src/lib/shared/cacheStores.ts +10 -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 +104 -0
- package/src/lib/shared/createChannelLog.ts +122 -0
- package/src/lib/shared/createLifecycleChannel.ts +56 -0
- package/src/lib/shared/createLivenessWatch.ts +118 -0
- package/src/lib/shared/createPushIterator.ts +127 -0
- package/src/lib/shared/createRemoteFunction.ts +122 -0
- package/src/lib/shared/createSubscriber.ts +55 -0
- package/src/lib/shared/createTraceContext.ts +21 -0
- package/src/lib/shared/dataDirEnvPath.ts +12 -0
- package/src/lib/shared/decodeResponse.ts +47 -0
- package/src/lib/shared/detectTarget.ts +27 -0
- package/src/lib/shared/detectVerbMethod.ts +17 -0
- package/src/lib/shared/emitLogRecord.ts +190 -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 +476 -0
- package/src/lib/shared/formatTraceparent.ts +6 -0
- package/src/lib/shared/forwardHeaders.ts +44 -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/health.ts +179 -0
- package/src/lib/shared/healthReadSlot.ts +11 -0
- package/src/lib/shared/healthSeedSlot.ts +12 -0
- package/src/lib/shared/html.ts +38 -0
- package/src/lib/shared/importNamesToStrip.ts +13 -0
- package/src/lib/shared/invalidateEvent.ts +11 -0
- package/src/lib/shared/invalidateTripwire.ts +40 -0
- package/src/lib/shared/isAbideHealthPayload.ts +11 -0
- package/src/lib/shared/isCompileTarget.ts +15 -0
- package/src/lib/shared/isDebugEnabled.ts +26 -0
- package/src/lib/shared/isDebugNegated.ts +19 -0
- package/src/lib/shared/isModuleNotFound.ts +16 -0
- package/src/lib/shared/isReadOnlyMethod.ts +14 -0
- package/src/lib/shared/isReplayableMethod.ts +7 -0
- package/src/lib/shared/isStreamingResponse.ts +11 -0
- package/src/lib/shared/isSubscribable.ts +15 -0
- package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
- package/src/lib/shared/jsonSchemaForSchema.ts +39 -0
- package/src/lib/shared/jsonlErrorFrame.ts +24 -0
- package/src/lib/shared/keyForRemoteCall.ts +29 -0
- package/src/lib/shared/keyMatchesPrefix.ts +9 -0
- package/src/lib/shared/lastConnectionPath.ts +7 -0
- package/src/lib/shared/layoutChainForRoute.ts +22 -0
- package/src/lib/shared/loadEnvFile.ts +17 -0
- package/src/lib/shared/loadEnvFromDataDir.ts +14 -0
- package/src/lib/shared/log.ts +24 -0
- package/src/lib/shared/logClosingRecord.ts +28 -0
- package/src/lib/shared/logTapSlot.ts +13 -0
- package/src/lib/shared/manifestModule.ts +39 -0
- package/src/lib/shared/matchesDebugPattern.ts +16 -0
- package/src/lib/shared/memoizeByKey.ts +32 -0
- package/src/lib/shared/normalizeTarget.ts +10 -0
- package/src/lib/shared/online.ts +51 -0
- package/src/lib/shared/page.ts +30 -0
- package/src/lib/shared/pageSlot.ts +17 -0
- package/src/lib/shared/pageUrlForFile.ts +14 -0
- package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
- package/src/lib/shared/parseDebugPatterns.ts +21 -0
- package/src/lib/shared/parseEnv.ts +30 -0
- package/src/lib/shared/parsePromptMarkdown.ts +35 -0
- package/src/lib/shared/parseRouteSegments.ts +22 -0
- package/src/lib/shared/parseTraceparent.ts +26 -0
- package/src/lib/shared/pending.ts +30 -0
- package/src/lib/shared/prepareRpcModule.ts +59 -0
- package/src/lib/shared/prepareSocketModule.ts +49 -0
- package/src/lib/shared/probeRegistries.ts +68 -0
- package/src/lib/shared/producerKey.ts +32 -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/randomHexId.ts +14 -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/refreshing.ts +31 -0
- package/src/lib/shared/remoteMetaStore.ts +16 -0
- package/src/lib/shared/requestScopeSlot.ts +15 -0
- package/src/lib/shared/resolveClientFlags.ts +20 -0
- package/src/lib/shared/responseErrorText.ts +9 -0
- package/src/lib/shared/rpcTimeoutSlot.ts +9 -0
- package/src/lib/shared/rpcUrlForFile.ts +19 -0
- package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
- package/src/lib/shared/selectorMatcher.ts +68 -0
- package/src/lib/shared/selectorPrefix.ts +39 -0
- package/src/lib/shared/serializeEnv.ts +18 -0
- package/src/lib/shared/setAppName.ts +5 -0
- package/src/lib/shared/setBaseResolver.ts +6 -0
- package/src/lib/shared/setCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setPageResolver.ts +7 -0
- package/src/lib/shared/setRequestScopeResolver.ts +6 -0
- package/src/lib/shared/snippet.ts +25 -0
- package/src/lib/shared/socketNameForFile.ts +11 -0
- package/src/lib/shared/socketTapSlot.ts +12 -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/tailProbeSlot.ts +16 -0
- package/src/lib/shared/toBunRoutePattern.ts +28 -0
- package/src/lib/shared/toScopeSet.ts +4 -0
- package/src/lib/shared/trace.ts +16 -0
- package/src/lib/shared/types/CacheEntry.ts +84 -0
- package/src/lib/shared/types/CacheInvalidation.ts +9 -0
- package/src/lib/shared/types/CacheOnContext.ts +25 -0
- package/src/lib/shared/types/CacheOptions.ts +39 -0
- package/src/lib/shared/types/CacheSelector.ts +17 -0
- package/src/lib/shared/types/CacheSnapshot.ts +16 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +17 -0
- package/src/lib/shared/types/CacheStats.ts +13 -0
- package/src/lib/shared/types/CacheStore.ts +39 -0
- package/src/lib/shared/types/ChannelLog.ts +13 -0
- package/src/lib/shared/types/ClientFlags.ts +11 -0
- package/src/lib/shared/types/CompileTarget.ts +6 -0
- package/src/lib/shared/types/FrameworkLog.ts +13 -0
- package/src/lib/shared/types/HttpVerb.ts +1 -0
- package/src/lib/shared/types/LastConnection.ts +9 -0
- package/src/lib/shared/types/Log.ts +13 -0
- package/src/lib/shared/types/LogRecord.ts +42 -0
- package/src/lib/shared/types/LogVoice.ts +7 -0
- package/src/lib/shared/types/PageSnapshot.ts +14 -0
- package/src/lib/shared/types/PromptArgument.ts +12 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +14 -0
- package/src/lib/shared/types/RemoteCallable.ts +12 -0
- package/src/lib/shared/types/RemoteFunction.ts +47 -0
- package/src/lib/shared/types/ReplayableMethod.ts +7 -0
- package/src/lib/shared/types/RequestScopeInfo.ts +16 -0
- package/src/lib/shared/types/RpcInvoker.ts +6 -0
- package/src/lib/shared/types/SocketChannel.ts +17 -0
- package/src/lib/shared/types/SocketSubCallbacks.ts +13 -0
- package/src/lib/shared/types/StandardSchemaV1.ts +56 -0
- package/src/lib/shared/types/StreamedResolution.ts +10 -0
- package/src/lib/shared/types/Subscribable.ts +26 -0
- package/src/lib/shared/types/TailHooks.ts +12 -0
- package/src/lib/shared/types/TailOptions.ts +10 -0
- package/src/lib/shared/types/TraceContext.ts +17 -0
- package/src/lib/shared/url.ts +118 -0
- package/src/lib/shared/withBase.ts +11 -0
- package/src/lib/shared/withBaseUrl.ts +17 -0
- package/src/lib/shared/withJsonSchema.ts +21 -0
- package/src/lib/shared/writeDts.ts +12 -0
- package/src/lib/shared/writeHealthDts.ts +36 -0
- package/src/lib/shared/writeLastConnection.ts +13 -0
- package/src/lib/shared/writePublicAssetsDts.ts +31 -0
- package/src/lib/shared/writeRoutesDts.ts +73 -0
- package/src/lib/shared/writeRpcDts.ts +49 -0
- package/src/lib/shared/writeTestRpcDts.ts +45 -0
- package/src/lib/shared/writeTestSocketsDts.ts +34 -0
- package/src/lib/test/assertAgentFrameConformance.ts +73 -0
- package/src/lib/test/createScriptedSurface.ts +45 -0
- package/src/lib/test/createTestApp.ts +203 -0
- package/src/lib/test/createTestSocketChannel.ts +142 -0
- package/src/lib/ui/README.md +86 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +25 -0
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +36 -0
- package/src/lib/ui/compile/VOID_TAGS.ts +21 -0
- package/src/lib/ui/compile/abideUiPlugin.ts +65 -0
- package/src/lib/ui/compile/analyzeComponent.ts +117 -0
- package/src/lib/ui/compile/assetModulesFile.ts +32 -0
- package/src/lib/ui/compile/branchElements.ts +50 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +59 -0
- package/src/lib/ui/compile/compileComponent.ts +20 -0
- package/src/lib/ui/compile/compileModule.ts +116 -0
- package/src/lib/ui/compile/compileSSR.ts +36 -0
- package/src/lib/ui/compile/compileShadow.ts +352 -0
- package/src/lib/ui/compile/createShadowLanguageService.ts +197 -0
- package/src/lib/ui/compile/createShadowProgram.ts +96 -0
- package/src/lib/ui/compile/decodeHtmlEntities.ts +49 -0
- package/src/lib/ui/compile/desugarSignals.ts +133 -0
- package/src/lib/ui/compile/escapeHtml.ts +15 -0
- package/src/lib/ui/compile/generateBuild.ts +638 -0
- package/src/lib/ui/compile/generateSSR.ts +380 -0
- package/src/lib/ui/compile/groupBindParts.ts +28 -0
- package/src/lib/ui/compile/hoistCells.ts +120 -0
- package/src/lib/ui/compile/loadShadowTsConfig.ts +31 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +202 -0
- package/src/lib/ui/compile/nearestProjectRoot.ts +16 -0
- package/src/lib/ui/compile/parseTemplate.ts +396 -0
- package/src/lib/ui/compile/partitionSlots.ts +36 -0
- package/src/lib/ui/compile/prepareNestedScript.ts +42 -0
- package/src/lib/ui/compile/remapShadowDiagnostic.ts +30 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +85 -0
- package/src/lib/ui/compile/resolveAbideImports.ts +29 -0
- package/src/lib/ui/compile/scopeCss.ts +115 -0
- package/src/lib/ui/compile/shadowNaming.ts +11 -0
- package/src/lib/ui/compile/sourceToShadowOffset.ts +24 -0
- package/src/lib/ui/compile/staticAttrValue.ts +13 -0
- package/src/lib/ui/compile/stripEffects.ts +32 -0
- package/src/lib/ui/compile/types/AbideDiagnostic.ts +14 -0
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +25 -0
- package/src/lib/ui/compile/types/CompiledShadow.ts +15 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +16 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +78 -0
- package/src/lib/ui/compile/types/TextPart.ts +8 -0
- package/src/lib/ui/derived.ts +28 -0
- package/src/lib/ui/doc.ts +15 -0
- package/src/lib/ui/dom/appendSnippet.ts +34 -0
- package/src/lib/ui/dom/appendStatic.ts +27 -0
- package/src/lib/ui/dom/appendText.ts +114 -0
- package/src/lib/ui/dom/applyResolved.ts +72 -0
- package/src/lib/ui/dom/attach.ts +20 -0
- package/src/lib/ui/dom/attr.ts +19 -0
- package/src/lib/ui/dom/awaitBlock.ts +224 -0
- package/src/lib/ui/dom/cloneStatic.ts +52 -0
- package/src/lib/ui/dom/each.ts +115 -0
- package/src/lib/ui/dom/eachAsync.ts +153 -0
- package/src/lib/ui/dom/hydrate.ts +35 -0
- package/src/lib/ui/dom/mount.ts +29 -0
- package/src/lib/ui/dom/mountChild.ts +33 -0
- package/src/lib/ui/dom/on.ts +15 -0
- package/src/lib/ui/dom/openChild.ts +22 -0
- package/src/lib/ui/dom/openRoot.ts +20 -0
- package/src/lib/ui/dom/switchBlock.ts +75 -0
- package/src/lib/ui/dom/text.ts +20 -0
- package/src/lib/ui/dom/tryBlock.ts +112 -0
- package/src/lib/ui/dom/types/EachRow.ts +3 -0
- package/src/lib/ui/dom/types/SwitchCase.ts +6 -0
- package/src/lib/ui/dom/when.ts +73 -0
- package/src/lib/ui/effect.ts +16 -0
- package/src/lib/ui/installHotBridge.ts +73 -0
- package/src/lib/ui/matchRoute.ts +89 -0
- package/src/lib/ui/navigate.ts +17 -0
- package/src/lib/ui/probeNavigation.ts +33 -0
- package/src/lib/ui/remoteProxy.ts +97 -0
- package/src/lib/ui/renderChain.ts +50 -0
- package/src/lib/ui/renderToStream.ts +104 -0
- package/src/lib/ui/router.ts +286 -0
- package/src/lib/ui/runtime/OUTLET_TAG.ts +8 -0
- package/src/lib/ui/runtime/OWNER.ts +8 -0
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +14 -0
- package/src/lib/ui/runtime/RENDER.ts +23 -0
- package/src/lib/ui/runtime/RESUME.ts +16 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +41 -0
- package/src/lib/ui/runtime/claimChild.ts +10 -0
- package/src/lib/ui/runtime/clientPage.ts +16 -0
- package/src/lib/ui/runtime/createComputedNode.ts +16 -0
- package/src/lib/ui/runtime/createDoc.ts +177 -0
- package/src/lib/ui/runtime/createEffectNode.ts +58 -0
- package/src/lib/ui/runtime/createSignalNode.ts +16 -0
- package/src/lib/ui/runtime/detachLink.ts +21 -0
- package/src/lib/ui/runtime/endTracking.ts +24 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +12 -0
- package/src/lib/ui/runtime/exitRenderPass.ts +7 -0
- package/src/lib/ui/runtime/firstOutlet.ts +22 -0
- package/src/lib/ui/runtime/flushEffects.ts +17 -0
- package/src/lib/ui/runtime/hotInstances.ts +10 -0
- package/src/lib/ui/runtime/hotReloadEnabled.ts +8 -0
- package/src/lib/ui/runtime/hotReplace.ts +25 -0
- package/src/lib/ui/runtime/nextBlockId.ts +11 -0
- package/src/lib/ui/runtime/pathExists.ts +23 -0
- package/src/lib/ui/runtime/readNode.ts +17 -0
- package/src/lib/ui/runtime/registerHotInstance.ts +23 -0
- package/src/lib/ui/runtime/runNode.ts +28 -0
- package/src/lib/ui/runtime/runtimePath.ts +9 -0
- package/src/lib/ui/runtime/scope.ts +24 -0
- package/src/lib/ui/runtime/toTeardown.ts +26 -0
- package/src/lib/ui/runtime/track.ts +58 -0
- package/src/lib/ui/runtime/trigger.ts +44 -0
- package/src/lib/ui/runtime/types/Cell.ts +5 -0
- package/src/lib/ui/runtime/types/Derived.ts +3 -0
- package/src/lib/ui/runtime/types/Doc.ts +19 -0
- package/src/lib/ui/runtime/types/EffectResult.ts +10 -0
- package/src/lib/ui/runtime/types/HotInstance.ts +14 -0
- package/src/lib/ui/runtime/types/NavVerdict.ts +9 -0
- package/src/lib/ui/runtime/types/Patch.ts +11 -0
- package/src/lib/ui/runtime/types/ReactiveLink.ts +21 -0
- package/src/lib/ui/runtime/types/ReactiveNode.ts +25 -0
- package/src/lib/ui/runtime/types/Route.ts +8 -0
- package/src/lib/ui/runtime/types/RouteLoader.ts +7 -0
- package/src/lib/ui/runtime/types/SsrRender.ts +22 -0
- package/src/lib/ui/runtime/types/State.ts +3 -0
- package/src/lib/ui/runtime/types/Teardown.ts +5 -0
- package/src/lib/ui/runtime/types/UiComponent.ts +16 -0
- package/src/lib/ui/runtime/types/UiProps.ts +15 -0
- package/src/lib/ui/runtime/unlinkDeps.ts +20 -0
- package/src/lib/ui/runtime/untrack.ts +20 -0
- package/src/lib/ui/runtime/valueAtPath.ts +18 -0
- package/src/lib/ui/runtime/writeNode.ts +16 -0
- package/src/lib/ui/socketChannel.ts +227 -0
- package/src/lib/ui/socketProxy.ts +25 -0
- package/src/lib/ui/startClient.ts +58 -0
- package/src/lib/ui/state.ts +25 -0
- package/src/lib/ui/tail.ts +324 -0
- package/src/lib/ui/types/Layouts.ts +9 -0
- package/src/lib/ui/types/Pages.ts +8 -0
- package/src/preload.ts +19 -0
- package/src/scaffold.ts +153 -0
- package/src/serverBuildPlugins.ts +19 -0
- package/src/serverEntry.ts +95 -0
- package/template/bunfig.toml +4 -0
- package/template/package.json +18 -0
- package/template/src/app.ts +28 -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 +36 -0
- package/template/src/ui/Layout.abide +19 -0
- package/template/src/ui/app.css +21 -0
- package/template/src/ui/app.html +24 -0
- package/template/src/ui/pages/about/page.abide +9 -0
- package/template/src/ui/pages/page.abide +22 -0
- package/template/test/app.test.ts +30 -0
- package/template/tsconfig.json +18 -0
- package/tsconfig.app.json +17 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises'
|
|
2
|
+
import { bindConnectedFlag } from './lib/bundle/bindConnectedFlag.ts'
|
|
3
|
+
import { bindRequestNavigate } from './lib/bundle/bindRequestNavigate.ts'
|
|
4
|
+
import { listenLocalControlServer } from './lib/bundle/listenLocalControlServer.ts'
|
|
5
|
+
import { probeAbideServer } from './lib/bundle/probeAbideServer.ts'
|
|
6
|
+
import { resolveWebviewLib } from './lib/bundle/resolveWebviewLib.ts'
|
|
7
|
+
import { spawnEmbeddedServer } from './lib/bundle/spawnEmbeddedServer.ts'
|
|
8
|
+
import { stableLocalPort } from './lib/bundle/stableLocalPort.ts'
|
|
9
|
+
import { abideLog } from './lib/shared/abideLog.ts'
|
|
10
|
+
import { appDataDir } from './lib/shared/appDataDir.ts'
|
|
11
|
+
import { binaryDirEnvPath } from './lib/shared/binaryDirEnvPath.ts'
|
|
12
|
+
import { clearLastConnection } from './lib/shared/clearLastConnection.ts'
|
|
13
|
+
import { createLivenessWatch } from './lib/shared/createLivenessWatch.ts'
|
|
14
|
+
import { dataDirEnvPath } from './lib/shared/dataDirEnvPath.ts'
|
|
15
|
+
import { readEnvFile } from './lib/shared/readEnvFile.ts'
|
|
16
|
+
import { readLastConnection } from './lib/shared/readLastConnection.ts'
|
|
17
|
+
import { serializeEnv } from './lib/shared/serializeEnv.ts'
|
|
18
|
+
import { writeLastConnection } from './lib/shared/writeLastConnection.ts'
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
The bundle's control server, run in a Worker so it owns its own thread.
|
|
22
|
+
|
|
23
|
+
`webview_run` enters a native UI run loop that blocks the launcher's main thread
|
|
24
|
+
indefinitely (the window owns it until close), which freezes the main thread's
|
|
25
|
+
JS event loop. An in-process `Bun.serve` there can never answer a request, so the
|
|
26
|
+
webview pointed at it would only ever see a hung navigation — a blank window.
|
|
27
|
+
|
|
28
|
+
Running the control server on this Worker thread keeps it answering the whole time
|
|
29
|
+
the window is open. It owns the pieces that must live beside it: the embedded
|
|
30
|
+
server child it spawns, and its own FFI handle to the native menu flag (set here
|
|
31
|
+
because the main thread can't process a postMessage while blocked in webview_run,
|
|
32
|
+
yet the flag is a process-global the main-thread menu still reads).
|
|
33
|
+
|
|
34
|
+
Bun does not apply the launcher build's plugins to a worker entry, so this module
|
|
35
|
+
can't import abide's virtual modules (the connect-screen HTML, app title). The
|
|
36
|
+
launcher — which can — passes them in the `init` message; on `shutdown` it has us
|
|
37
|
+
reap the embedded child before the launcher exits.
|
|
38
|
+
|
|
39
|
+
Once connected it also watches the chosen server's liveness — polling its identity
|
|
40
|
+
endpoint — and, when it stops answering, corrects the menu flag and bounces the
|
|
41
|
+
window back to the connect screen, since a dead server (local crash or remote
|
|
42
|
+
outage) otherwise leaves a frozen page and a menu that still claims connected.
|
|
43
|
+
|
|
44
|
+
GET / → the connect screen (title injected at serve time)
|
|
45
|
+
GET /__abide/config → { schema, values } for the first-run config form
|
|
46
|
+
POST /__abide/config → persist the form's answers to the data-dir .env
|
|
47
|
+
POST /connect {url} → record connected, reply { redirect: url }
|
|
48
|
+
POST /start → spawn the server binary, reply { redirect: localUrl }
|
|
49
|
+
GET /__abide/disconnect → reap the child, clear connected
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
Init payload from the launcher, plus the per-run state the handlers close over.
|
|
54
|
+
`configSchema` is the JSON Schema derived from the app's BundleWindow.config
|
|
55
|
+
(undefined when none declared), driving the connect screen's first-run form.
|
|
56
|
+
*/
|
|
57
|
+
type Init = {
|
|
58
|
+
disconnectedHtml: string
|
|
59
|
+
title: string
|
|
60
|
+
programName: string
|
|
61
|
+
configSchema?: Record<string, unknown>
|
|
62
|
+
}
|
|
63
|
+
let disconnectedHtml = ''
|
|
64
|
+
let title = ''
|
|
65
|
+
let programName = ''
|
|
66
|
+
let configSchema: Record<string, unknown> | undefined
|
|
67
|
+
let flag: ReturnType<typeof bindConnectedFlag> | undefined
|
|
68
|
+
let server: ReturnType<typeof listenLocalControlServer> | undefined
|
|
69
|
+
|
|
70
|
+
// The control-server origin (where the connect screen lives) and the webview
|
|
71
|
+
// handle, forwarded by the launcher — together they let the watch bounce a dead
|
|
72
|
+
// window back to the connect screen.
|
|
73
|
+
let controlOrigin = ''
|
|
74
|
+
let navigate: ReturnType<typeof bindRequestNavigate> | undefined
|
|
75
|
+
let webviewHandle: number | undefined
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
Liveness watch over the currently-connected server: the shared state machine
|
|
79
|
+
(createLivenessWatch) driven by the identity-endpoint probe — a couple of
|
|
80
|
+
consecutive misses, tolerating a transient blip or a quick restart, count as
|
|
81
|
+
a death. Stopped whenever we're not connected.
|
|
82
|
+
*/
|
|
83
|
+
const liveness = createLivenessWatch({
|
|
84
|
+
probe: async (url) => (await probeAbideServer(url)) !== undefined,
|
|
85
|
+
onLost: handleConnectionLost,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Embedded-server child, spawned on demand by Start server; undefined when none.
|
|
89
|
+
let serverChild: ReturnType<typeof Bun.spawn> | undefined
|
|
90
|
+
|
|
91
|
+
// Reaps the embedded server child if one is running.
|
|
92
|
+
function killServerChild(): void {
|
|
93
|
+
if (serverChild) {
|
|
94
|
+
serverChild.kill()
|
|
95
|
+
serverChild = undefined
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
Optional local-assistant bridge, tied to the connected app. Started only when the
|
|
101
|
+
app bundled @abide/claude-code (its UI uses browser/assistant, so the dynamic
|
|
102
|
+
import resolves — an app that doesn't is tolerated by --compile and no-ops) AND
|
|
103
|
+
`claude` is on PATH. The page receives the loopback bridge's port+token through the
|
|
104
|
+
webview URL fragment (withAssistant); no `claude` yields an `unavailable` fragment
|
|
105
|
+
so the page shows an install hint instead of a run-this-command prompt. The bridge
|
|
106
|
+
is loopback-only and torn down with the connection (stopLivenessWatch).
|
|
107
|
+
*/
|
|
108
|
+
let bridge: ReturnType<typeof import('@abide/claude-code/serve')['serve']> | undefined
|
|
109
|
+
let assistantHandshake: string | undefined
|
|
110
|
+
|
|
111
|
+
async function startBridge(url: string): Promise<void> {
|
|
112
|
+
const claude = await import('@abide/claude-code/serve').catch(() => undefined)
|
|
113
|
+
if (!claude) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
if (!Bun.which('claude')) {
|
|
117
|
+
assistantHandshake = 'unavailable'
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
const token = crypto.randomUUID()
|
|
121
|
+
const server = claude.serve({ url, allowOrigins: [url], token })
|
|
122
|
+
bridge = server
|
|
123
|
+
assistantHandshake = `${server.port}.${token}`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function stopBridge(): void {
|
|
127
|
+
bridge?.stop(true)
|
|
128
|
+
bridge = undefined
|
|
129
|
+
assistantHandshake = undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Append the bridge handshake to a webview URL so the page's assistant() finds its port+token.
|
|
133
|
+
function withAssistant(url: string): string {
|
|
134
|
+
return assistantHandshake ? `${url}#abide-assistant=${assistantHandshake}` : url
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Begin (or restart) watching `url` for liveness once the window points at it.
|
|
138
|
+
function startLivenessWatch(url: string): void {
|
|
139
|
+
liveness.watch(url)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Stop watching — on explicit disconnect, on detected death, or at shutdown.
|
|
143
|
+
function stopLivenessWatch(): void {
|
|
144
|
+
liveness.stop()
|
|
145
|
+
// The assistant bridge follows the connection — drop it whenever we disconnect.
|
|
146
|
+
stopBridge()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
The connected-state transition pair. Connected is three coordinated pieces —
|
|
151
|
+
the native menu flag, the liveness watch, and the assistant bridge — that must
|
|
152
|
+
move together; disconnected additionally reaps any embedded child. Every
|
|
153
|
+
handler routes through these so no path can desync the menu or leave a bridge
|
|
154
|
+
running against a dead server.
|
|
155
|
+
*/
|
|
156
|
+
async function becomeConnected(url: string): Promise<void> {
|
|
157
|
+
flag?.setConnected(true)
|
|
158
|
+
startLivenessWatch(url)
|
|
159
|
+
await startBridge(url)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function becomeDisconnected(): void {
|
|
163
|
+
stopLivenessWatch()
|
|
164
|
+
killServerChild()
|
|
165
|
+
flag?.setConnected(false)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
The connected server stopped answering. Tear the connection down and bounce the
|
|
170
|
+
window back to the connect screen with a `lost` notice. The flag flip alone keeps
|
|
171
|
+
the menu honest even when the navigate is a no-op (off macOS, or no handle yet).
|
|
172
|
+
*/
|
|
173
|
+
function handleConnectionLost(url: string): void {
|
|
174
|
+
abideLog.warn(`connected server stopped responding: ${url}`)
|
|
175
|
+
becomeDisconnected()
|
|
176
|
+
if (webviewHandle !== undefined) {
|
|
177
|
+
navigate?.requestNavigate(webviewHandle, `${controlOrigin}/?action=lost`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
Boots the embedded server via the shared spawn helper and keeps the child so
|
|
183
|
+
killServerChild can reap it. Any previous child is reaped first so only one
|
|
184
|
+
embedded server runs at a time; returns the URL to point the window at.
|
|
185
|
+
*/
|
|
186
|
+
async function startEmbeddedServer(timeoutMs?: number): Promise<string> {
|
|
187
|
+
killServerChild()
|
|
188
|
+
const { url, child } = await spawnEmbeddedServer({ programName, timeoutMs })
|
|
189
|
+
serverChild = child
|
|
190
|
+
return url
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
Where the window should point on launch, resolved before it ever opens so the
|
|
195
|
+
connect screen never flashes. Repeats the last connection from the launcher-owned
|
|
196
|
+
record (which survives relaunch where the embedded server's fresh port can't):
|
|
197
|
+
|
|
198
|
+
- embedded, config complete → boot it and point at the live server
|
|
199
|
+
- embedded, config missing → the connect screen, so the user can configure
|
|
200
|
+
- remote url, still alive → point straight at it
|
|
201
|
+
- remote url, now dead → the connect screen with a `lost` notice
|
|
202
|
+
- nothing recorded → the connect screen
|
|
203
|
+
|
|
204
|
+
Boot is bounded by a short ceiling: a failed or slow boot falls back to the
|
|
205
|
+
connect screen rather than leaving the launcher window-less, and reaps the child
|
|
206
|
+
so a half-started server doesn't hold its port.
|
|
207
|
+
*/
|
|
208
|
+
const AUTO_START_CEILING_MS = 3000
|
|
209
|
+
async function resolveLaunchTarget(): Promise<string> {
|
|
210
|
+
const last = await readLastConnection(programName)
|
|
211
|
+
if (!last) {
|
|
212
|
+
return controlOrigin
|
|
213
|
+
}
|
|
214
|
+
if (last.kind === 'embedded') {
|
|
215
|
+
if (await autoStartBlockedByConfig()) {
|
|
216
|
+
return controlOrigin
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const url = await startEmbeddedServer(AUTO_START_CEILING_MS)
|
|
220
|
+
await becomeConnected(url)
|
|
221
|
+
abideLog.info(`resumed embedded server at ${url}`)
|
|
222
|
+
return url
|
|
223
|
+
} catch (error) {
|
|
224
|
+
killServerChild()
|
|
225
|
+
abideLog.warn(`embedded server did not resume: ${String(error)}`)
|
|
226
|
+
return controlOrigin
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const identity = await probeAbideServer(last.url)
|
|
230
|
+
if (identity) {
|
|
231
|
+
await becomeConnected(last.url)
|
|
232
|
+
abideLog.info(`reconnected to ${identity.name} at ${last.url}`)
|
|
233
|
+
return last.url
|
|
234
|
+
}
|
|
235
|
+
abideLog.warn(`saved server did not respond: ${last.url}`)
|
|
236
|
+
return `${controlOrigin}/?action=lost`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// True when the app declares required config that nothing yet supplies, so an
|
|
240
|
+
// embedded auto-start would only crash for the lack of it — land on the connect
|
|
241
|
+
// screen (and its setup modal) instead.
|
|
242
|
+
async function autoStartBlockedByConfig(): Promise<boolean> {
|
|
243
|
+
const required = (configSchema?.required as string[] | undefined) ?? []
|
|
244
|
+
if (required.length === 0) {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
const values = await resolveConfigValues()
|
|
248
|
+
return required.some((key) => !values[key])
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/*
|
|
252
|
+
Injects the app title into the connect-screen HTML just before serving — the build
|
|
253
|
+
left a `<!--abide:connect-config-->` marker in <head>.
|
|
254
|
+
*/
|
|
255
|
+
function renderConnectScreen(): Response {
|
|
256
|
+
const script = `<script>window.__ABIDE_TITLE__=${JSON.stringify(title)}</script>`
|
|
257
|
+
const html = disconnectedHtml.replace('<!--abide:connect-config-->', script)
|
|
258
|
+
return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8' } })
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
Resolves the value to pre-fill each config field with, following the same
|
|
263
|
+
precedence the server applies below the shell: the user's saved data-dir `.env`,
|
|
264
|
+
then the bundle's shipped binary-dir `.env`, then the schema's own `default`.
|
|
265
|
+
Empty string when nothing supplies it — which is how the form spots an unmet
|
|
266
|
+
required field.
|
|
267
|
+
*/
|
|
268
|
+
async function resolveConfigValues(): Promise<Record<string, string>> {
|
|
269
|
+
const properties = (configSchema?.properties ?? {}) as Record<string, { default?: unknown }>
|
|
270
|
+
// Independent reads — fetch together; precedence is applied in the merge below.
|
|
271
|
+
const [dataDirEnv, binaryDirEnv] = await Promise.all([
|
|
272
|
+
readEnvFile(dataDirEnvPath(programName)),
|
|
273
|
+
readEnvFile(binaryDirEnvPath()),
|
|
274
|
+
])
|
|
275
|
+
return Object.fromEntries(
|
|
276
|
+
Object.keys(properties).map((key) => {
|
|
277
|
+
const fallback = properties[key]?.default
|
|
278
|
+
const value =
|
|
279
|
+
dataDirEnv[key] ??
|
|
280
|
+
binaryDirEnv[key] ??
|
|
281
|
+
(fallback === undefined ? '' : String(fallback))
|
|
282
|
+
return [key, value]
|
|
283
|
+
}),
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/*
|
|
288
|
+
Persists the form's answers to the data-dir `.env`, merged over any existing
|
|
289
|
+
file so keys the form didn't touch survive. Creates the data dir on first run
|
|
290
|
+
(appDataDir only computes the path).
|
|
291
|
+
*/
|
|
292
|
+
async function writeConfig(values: Record<string, string>): Promise<void> {
|
|
293
|
+
const path = dataDirEnvPath(programName)
|
|
294
|
+
const merged = { ...(await readEnvFile(path)), ...values }
|
|
295
|
+
await mkdir(appDataDir(programName), { recursive: true })
|
|
296
|
+
await Bun.write(path, serializeEnv(merged))
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// GET /__abide/config — the form's schema + current values, or null schema to skip the gate.
|
|
300
|
+
async function handleConfigGet(): Promise<Response> {
|
|
301
|
+
if (!configSchema) {
|
|
302
|
+
return Response.json({ schema: null, values: {} })
|
|
303
|
+
}
|
|
304
|
+
return Response.json({ schema: configSchema, values: await resolveConfigValues() })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// POST /__abide/config — persist the form's answers to the data-dir `.env`.
|
|
308
|
+
async function handleConfigPost(request: Request): Promise<Response> {
|
|
309
|
+
const { values } = (await request.json()) as { values: Record<string, string> }
|
|
310
|
+
await writeConfig(values)
|
|
311
|
+
return new Response(undefined, { status: 204 })
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// POST /connect — point the window at a remote abide server after probing it.
|
|
315
|
+
async function handleConnect(request: Request): Promise<Response> {
|
|
316
|
+
const { url: target } = (await request.json()) as { url: string }
|
|
317
|
+
// Verify it's actually a abide server before pointing the window at it.
|
|
318
|
+
const identity = await probeAbideServer(target)
|
|
319
|
+
if (!identity) {
|
|
320
|
+
abideLog.warn(`no abide server responded at ${target}`)
|
|
321
|
+
return Response.json({ error: `No abide server responded at ${target}` }, { status: 502 })
|
|
322
|
+
}
|
|
323
|
+
await becomeConnected(target)
|
|
324
|
+
// Record the choice so the next launch reconnects here before opening.
|
|
325
|
+
await writeLastConnection(programName, { kind: 'url', url: target })
|
|
326
|
+
abideLog.info(`connecting to ${identity.name} at ${target}`)
|
|
327
|
+
return Response.json({ redirect: withAssistant(target) })
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// POST /start — boot the embedded server and point the window at it.
|
|
331
|
+
async function handleStart(): Promise<Response> {
|
|
332
|
+
try {
|
|
333
|
+
const localUrl = await startEmbeddedServer()
|
|
334
|
+
await becomeConnected(localUrl)
|
|
335
|
+
// Record the choice so the next launch boots the embedded server first.
|
|
336
|
+
await writeLastConnection(programName, { kind: 'embedded' })
|
|
337
|
+
abideLog.info(`started embedded server at ${localUrl}`)
|
|
338
|
+
return Response.json({ redirect: withAssistant(localUrl) })
|
|
339
|
+
} catch (error) {
|
|
340
|
+
killServerChild()
|
|
341
|
+
return Response.json({ error: String(error) }, { status: 500 })
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// GET /__abide/disconnect — tear down the embedded server and forget the auto-resume choice.
|
|
346
|
+
async function handleDisconnect(): Promise<Response> {
|
|
347
|
+
becomeDisconnected()
|
|
348
|
+
await clearLastConnection(programName)
|
|
349
|
+
return new Response(undefined, { status: 204 })
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/*
|
|
353
|
+
The control server's routes, keyed by `${method} ${pathname}` (exact match). The
|
|
354
|
+
connect screen owns localStorage + navigation; this worker owns the embedded-
|
|
355
|
+
server process and the native flag.
|
|
356
|
+
*/
|
|
357
|
+
const controlRoutes: Record<string, (request: Request) => Promise<Response> | Response> = {
|
|
358
|
+
'GET /': () => renderConnectScreen(),
|
|
359
|
+
'GET /__abide/config': handleConfigGet,
|
|
360
|
+
'POST /__abide/config': handleConfigPost,
|
|
361
|
+
'POST /connect': handleConnect,
|
|
362
|
+
'POST /start': handleStart,
|
|
363
|
+
'GET /__abide/disconnect': handleDisconnect,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function handleControlRequest(request: Request): Promise<Response> | Response {
|
|
367
|
+
const { pathname } = new URL(request.url)
|
|
368
|
+
const route = controlRoutes[`${request.method} ${pathname}`]
|
|
369
|
+
return route ? route(request) : new Response('not found', { status: 404 })
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/*
|
|
373
|
+
Bind the control server to 127.0.0.1 literally (not `localhost`) so the webview
|
|
374
|
+
reaches it without any IPv4/IPv6 name-resolution ambiguity, open the native flag
|
|
375
|
+
handle, then resolve where the window should open before handing back. Resolving
|
|
376
|
+
the launch target here — booting/probing the last connection before `ready` — is
|
|
377
|
+
what lets the launcher open the window straight at the live server, so the
|
|
378
|
+
connect screen never flashes; only an unconfigured, failed, or absent resume
|
|
379
|
+
falls back to it. The launcher gets both `origin` (for the File-menu actions) and
|
|
380
|
+
`target` (where to point the window now).
|
|
381
|
+
*/
|
|
382
|
+
async function start(init: Init): Promise<void> {
|
|
383
|
+
disconnectedHtml = init.disconnectedHtml
|
|
384
|
+
title = init.title
|
|
385
|
+
programName = init.programName
|
|
386
|
+
configSchema = init.configSchema
|
|
387
|
+
const libPath = await resolveWebviewLib()
|
|
388
|
+
flag = bindConnectedFlag(libPath)
|
|
389
|
+
navigate = bindRequestNavigate(libPath)
|
|
390
|
+
server = listenLocalControlServer(stableLocalPort(init.programName), handleControlRequest)
|
|
391
|
+
controlOrigin = `http://127.0.0.1:${server.port}`
|
|
392
|
+
abideLog.info(`${title} control server listening at ${controlOrigin}`)
|
|
393
|
+
const target = await resolveLaunchTarget()
|
|
394
|
+
// Fold in the assistant handshake (when a bridge started) so the page is configured on first load.
|
|
395
|
+
self.postMessage({ type: 'ready', origin: controlOrigin, target: withAssistant(target) })
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Reap the child + release the server and FFI handles, then confirm so the
|
|
399
|
+
// launcher can exit cleanly.
|
|
400
|
+
function shutdown(): void {
|
|
401
|
+
stopLivenessWatch()
|
|
402
|
+
killServerChild()
|
|
403
|
+
server?.stop(true)
|
|
404
|
+
flag?.close()
|
|
405
|
+
navigate?.close()
|
|
406
|
+
self.postMessage({ type: 'shutdownDone' })
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/*
|
|
410
|
+
The launcher drives the lifecycle: `init` (with the data this worker can't import)
|
|
411
|
+
starts the server, `window` forwards the webview handle the liveness watch needs to
|
|
412
|
+
navigate, and `shutdown` tears it all down once the window closes.
|
|
413
|
+
*/
|
|
414
|
+
self.addEventListener('message', (event: MessageEvent) => {
|
|
415
|
+
const data = event.data as
|
|
416
|
+
| { type: 'init'; init: Init }
|
|
417
|
+
| { type: 'window'; handle: number }
|
|
418
|
+
| { type: 'shutdown' }
|
|
419
|
+
if (data.type === 'init') {
|
|
420
|
+
void start(data.init)
|
|
421
|
+
} else if (data.type === 'window') {
|
|
422
|
+
webviewHandle = data.handle
|
|
423
|
+
} else if (data.type === 'shutdown') {
|
|
424
|
+
shutdown()
|
|
425
|
+
}
|
|
426
|
+
})
|
package/src/devEntry.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { watch } from 'node:fs'
|
|
2
|
+
import type { Subprocess } from 'bun'
|
|
3
|
+
import { build } from './build.ts'
|
|
4
|
+
import { DEFAULT_PORT } from './lib/server/runtime/DEFAULT_PORT.ts'
|
|
5
|
+
import { DEV_READY_MESSAGE } from './lib/server/runtime/DEV_READY_MESSAGE.ts'
|
|
6
|
+
import { DEV_REBUILD_MESSAGE } from './lib/server/runtime/DEV_REBUILD_MESSAGE.ts'
|
|
7
|
+
import { findOpenPort } from './lib/server/runtime/findOpenPort.ts'
|
|
8
|
+
import { abideLog } from './lib/shared/abideLog.ts'
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
Dev orchestrator. Replaces `bun --watch` (which only watches the import graph,
|
|
12
|
+
so new files / CSS / public assets never triggered a restart) with an explicit
|
|
13
|
+
loop we own end to end:
|
|
14
|
+
|
|
15
|
+
1. Build the client once — uncompressed, unminified (compressing on every
|
|
16
|
+
rebuild dwarfs the bundle; the server serves the plain bytes when no .gz exists).
|
|
17
|
+
2. Spawn the server as a child against a fixed dev port and ABIDE_DEV=1, which
|
|
18
|
+
makes it mount the /__abide/dev live-reload channel.
|
|
19
|
+
3. Watch src/ recursively. On any change, rebuild then swap the worker: the
|
|
20
|
+
replacement boots alongside the incumbent (both bind the port via
|
|
21
|
+
reusePort) and the incumbent is retired only once the replacement reports
|
|
22
|
+
ready over IPC — the port keeps answering across the whole restart. SSR
|
|
23
|
+
renders pages through Bun's module cache, so a fresh module graph (a new
|
|
24
|
+
process) is the reliable way to reflect a source edit — Bun has no stable
|
|
25
|
+
in-process invalidation. Killing the incumbent drops the browser's
|
|
26
|
+
live-reload channel; it reconnects straight onto the already-listening
|
|
27
|
+
replacement and reloads itself.
|
|
28
|
+
|
|
29
|
+
Restarts are serialized (a build mid-flight queues the next) and the port is
|
|
30
|
+
fixed so the browser tab stays valid across restarts. A failed build — or a
|
|
31
|
+
replacement that dies or hangs while booting — keeps the last-good server
|
|
32
|
+
running rather than tearing the loop down. A worker that crashes while active
|
|
33
|
+
is respawned, bounded so a boot-time crash loop gives up and waits for a save.
|
|
34
|
+
*/
|
|
35
|
+
const cwd = process.cwd()
|
|
36
|
+
const PRELOAD = new URL('./preload.ts', import.meta.url).pathname
|
|
37
|
+
const SERVER_ENTRY = new URL('./serverEntry.ts', import.meta.url).pathname
|
|
38
|
+
const SOURCE_DIR = `${cwd}/src`
|
|
39
|
+
// Coalesce editor save bursts (and multi-file saves) into one rebuild.
|
|
40
|
+
const REBUILD_DEBOUNCE_MS = 60
|
|
41
|
+
// How long a booting replacement gets to report ready before it is discarded.
|
|
42
|
+
const READY_TIMEOUT_MS = 30000
|
|
43
|
+
// Consecutive worker exits with no ready signal in between before the
|
|
44
|
+
// crash-respawn loop gives up and waits for the next save.
|
|
45
|
+
const MAX_EXITS_WITHOUT_READY = 3
|
|
46
|
+
/*
|
|
47
|
+
Generated dir the build itself writes into src/ (route type declarations). It
|
|
48
|
+
must be ignored or each rebuild's write retriggers the watcher — an endless
|
|
49
|
+
rebuild loop.
|
|
50
|
+
*/
|
|
51
|
+
const GENERATED_DIR = '.abide'
|
|
52
|
+
|
|
53
|
+
// True for paths under src/.abide (the build's own generated output).
|
|
54
|
+
function isGenerated(filename: string): boolean {
|
|
55
|
+
return filename.split(/[\\/]/).includes(GENERATED_DIR)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// clean:false leaves the live dist in place — each build swaps _app in atomically,
|
|
59
|
+
// so the running server never serves a half-built or emptied bundle.
|
|
60
|
+
const buildOptions = {
|
|
61
|
+
cwd,
|
|
62
|
+
minify: false,
|
|
63
|
+
compress: false,
|
|
64
|
+
clean: false,
|
|
65
|
+
exitOnFailure: false,
|
|
66
|
+
} as const
|
|
67
|
+
|
|
68
|
+
// The worker currently meant to be serving; undefined while crashed or replaced.
|
|
69
|
+
let server: Subprocess | undefined
|
|
70
|
+
let shuttingDown = false
|
|
71
|
+
// Worker exits since the last ready signal — bounds the crash-respawn loop.
|
|
72
|
+
let exitsWithoutReady = 0
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
Spawn a server worker against the fixed dev port. `ready` resolves when the
|
|
76
|
+
worker reports its listener is up and init() has run (DEV_READY_MESSAGE) — the
|
|
77
|
+
cue that a replacement may retire its predecessor.
|
|
78
|
+
*/
|
|
79
|
+
function spawnWorker(port: number): { proc: Subprocess; ready: Promise<void> } {
|
|
80
|
+
const readiness = Promise.withResolvers<void>()
|
|
81
|
+
const proc = Bun.spawn({
|
|
82
|
+
cmd: ['bun', '--preload', PRELOAD, SERVER_ENTRY],
|
|
83
|
+
cwd,
|
|
84
|
+
env: { ...process.env, PORT: String(port), ABIDE_DEV: '1' },
|
|
85
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
86
|
+
// The child's POST /__abide/reload route signals a rebuild over IPC, so the
|
|
87
|
+
// trigger rides the app's own port instead of a side channel.
|
|
88
|
+
ipc(message) {
|
|
89
|
+
if (message === DEV_REBUILD_MESSAGE) {
|
|
90
|
+
void rebuild(port)
|
|
91
|
+
}
|
|
92
|
+
if (message === DEV_READY_MESSAGE) {
|
|
93
|
+
exitsWithoutReady = 0
|
|
94
|
+
readiness.resolve()
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
respawnOnUnexpectedExit(proc, port)
|
|
99
|
+
return { proc, ready: readiness.promise }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
A worker that dies while it is the active server — an app crash, not a swap or
|
|
104
|
+
shutdown — used to leave the port dead until a manual restart. Respawn it,
|
|
105
|
+
giving up after MAX_EXITS_WITHOUT_READY exits in a row so a crash-on-boot
|
|
106
|
+
doesn't spin; the next save retries through rebuild.
|
|
107
|
+
*/
|
|
108
|
+
function respawnOnUnexpectedExit(proc: Subprocess, port: number): void {
|
|
109
|
+
void proc.exited.then((exitCode) => {
|
|
110
|
+
if (shuttingDown || server !== proc) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
server = undefined
|
|
114
|
+
exitsWithoutReady += 1
|
|
115
|
+
if (exitsWithoutReady >= MAX_EXITS_WITHOUT_READY) {
|
|
116
|
+
abideLog.warn(
|
|
117
|
+
`server keeps exiting (code ${exitCode}) — fix the error and save to retry`,
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
abideLog.warn(`server exited unexpectedly (code ${exitCode}) — restarting`)
|
|
122
|
+
server = spawnWorker(port).proc
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Terminate a worker and wait for it to exit (SIGKILL watchdog for a wedged exit). */
|
|
127
|
+
async function stopWorker(proc: Subprocess): Promise<void> {
|
|
128
|
+
proc.kill()
|
|
129
|
+
const watchdog = setTimeout(() => proc.kill('SIGKILL'), 3000)
|
|
130
|
+
await proc.exited
|
|
131
|
+
clearTimeout(watchdog)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
Zero-downtime swap. The replacement overlaps the incumbent — both bind the dev
|
|
136
|
+
port via reusePort (see createServer) and the kernel keeps delivering
|
|
137
|
+
connections to the incumbent until it stops — so the port answers throughout
|
|
138
|
+
the new module graph's boot. Only a replacement that reports ready retires the
|
|
139
|
+
incumbent; one that dies or hangs booting is discarded and the last-good server
|
|
140
|
+
keeps serving, mirroring how a failed build is handled.
|
|
141
|
+
*/
|
|
142
|
+
async function replaceServer(port: number): Promise<void> {
|
|
143
|
+
const previous = server
|
|
144
|
+
const next = spawnWorker(port)
|
|
145
|
+
const outcome = await Promise.race([
|
|
146
|
+
next.ready.then(() => 'ready' as const),
|
|
147
|
+
next.proc.exited.then(() => 'exited' as const),
|
|
148
|
+
Bun.sleep(READY_TIMEOUT_MS).then(() => 'timeout' as const),
|
|
149
|
+
])
|
|
150
|
+
if (outcome !== 'ready') {
|
|
151
|
+
if (outcome === 'timeout') {
|
|
152
|
+
await stopWorker(next.proc)
|
|
153
|
+
}
|
|
154
|
+
abideLog.warn('new server failed to boot — the previous one (if any) keeps serving')
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
server = next.proc
|
|
158
|
+
if (previous) {
|
|
159
|
+
await stopWorker(previous)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let building = false
|
|
164
|
+
let queued = false
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
Rebuild the client, then (on success) swap in a fresh server child. Serialized:
|
|
168
|
+
a change arriving mid-build sets `queued` so exactly one more rebuild runs
|
|
169
|
+
after, collapsing any further changes in between. A failed build leaves the
|
|
170
|
+
current child untouched — the error is logged and the last-good server keeps
|
|
171
|
+
serving.
|
|
172
|
+
*/
|
|
173
|
+
async function rebuild(port: number): Promise<void> {
|
|
174
|
+
if (building) {
|
|
175
|
+
queued = true
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
building = true
|
|
179
|
+
try {
|
|
180
|
+
const succeeded = await build(buildOptions)
|
|
181
|
+
if (succeeded) {
|
|
182
|
+
await replaceServer(port)
|
|
183
|
+
}
|
|
184
|
+
} finally {
|
|
185
|
+
building = false
|
|
186
|
+
if (queued) {
|
|
187
|
+
queued = false
|
|
188
|
+
void rebuild(port)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
Pick a free port once and reuse it for every restart, so the browser tab keeps
|
|
195
|
+
pointing at the same address. Scans upward from the shared default so dev lands
|
|
196
|
+
on the same predictable 3000+ address as `bun start`; reusing the number across
|
|
197
|
+
restarts (not re-scanning) is what keeps the tab valid.
|
|
198
|
+
*/
|
|
199
|
+
const port = findOpenPort(DEFAULT_PORT)
|
|
200
|
+
const firstBuild = await build(buildOptions)
|
|
201
|
+
if (!firstBuild) {
|
|
202
|
+
abideLog.warn('initial build failed — fix the error and save to retry')
|
|
203
|
+
}
|
|
204
|
+
server = spawnWorker(port).proc
|
|
205
|
+
|
|
206
|
+
/*
|
|
207
|
+
ABIDE_DEV_NO_WATCH=1 skips the fs watcher: rebuild only on demand via POST
|
|
208
|
+
/__abide/reload (always mounted under dev), so a long-lived in-process job — e.g.
|
|
209
|
+
an agent editing the app's own source — isn't yanked mid-run by a save.
|
|
210
|
+
*/
|
|
211
|
+
const manualRebuild = Bun.env.ABIDE_DEV_NO_WATCH === '1'
|
|
212
|
+
|
|
213
|
+
let debounce: ReturnType<typeof setTimeout> | undefined
|
|
214
|
+
const watcher = manualRebuild
|
|
215
|
+
? undefined
|
|
216
|
+
: watch(SOURCE_DIR, { recursive: true }, (_event, filename) => {
|
|
217
|
+
if (!filename || isGenerated(filename)) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
clearTimeout(debounce)
|
|
221
|
+
debounce = setTimeout(() => void rebuild(port), REBUILD_DEBOUNCE_MS)
|
|
222
|
+
})
|
|
223
|
+
if (manualRebuild) {
|
|
224
|
+
abideLog.info(
|
|
225
|
+
`manual rebuild mode — POST http://localhost:${port}/__abide/reload to apply changes`,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Tear down the watcher and the child on shutdown so neither outlives the orchestrator. */
|
|
230
|
+
const shutdown = async () => {
|
|
231
|
+
shuttingDown = true
|
|
232
|
+
watcher?.close()
|
|
233
|
+
if (server) {
|
|
234
|
+
await stopWorker(server)
|
|
235
|
+
}
|
|
236
|
+
process.exit(0)
|
|
237
|
+
}
|
|
238
|
+
process.on('SIGINT', shutdown)
|
|
239
|
+
process.on('SIGTERM', shutdown)
|
|
240
|
+
process.on('SIGHUP', shutdown)
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
Last-resort sync cleanup: Bun.spawn'd children aren't reaped when the parent
|
|
244
|
+
dies, so a crash (uncaught error, terminal close) would otherwise leave the
|
|
245
|
+
server holding the dev port. 'exit' fires for every exit path; kill is
|
|
246
|
+
synchronous, which is enough to signal the child before we go.
|
|
247
|
+
*/
|
|
248
|
+
process.on('exit', () => {
|
|
249
|
+
server?.kill()
|
|
250
|
+
})
|