Unified · Split
| @@ -83,7 +83,7 @@ executor web · 82 unmodified lines | |||
| 83 | Uninstall: | 83 | Uninstall: |
| 84 | 84 | ||
| 85 | ```bash | 85 | ```bash |
| 86 | bash executor/uninstall --yes | 86 | executor uninstall --yes |
| 87 | ``` | 87 | ``` |
| 88 | 88 | ||
| 89 | Default managed-runtime ports: | 89 | Default managed-runtime ports: |
| @@ -164,7 +164,7 @@ export function ToolsView({ · 163 unmodified lines | |||
| 164 | {debug ? ( | 164 | {debug ? ( |
| 165 | <div className="space-y-0.5"> | 165 | <div className="space-y-0.5"> |
| 166 | <p className="text-[10px] font-mono text-muted-foreground/90"> | 166 | <p className="text-[10px] font-mono text-muted-foreground/90"> |
| 167 | debug mode={debug.mode} · cacheHit={String(debug.cacheHit)} · cacheFresh={String(debug.cacheFresh)} · duration={debug.durationMs}ms · sources={debug.normalizedSourceCount}/{debug.sourceCount} | 167 | debug mode={debug.mode} · cacheHit={String(debug.cacheHit)} · cacheFresh={String(debug.cacheFresh)} · skipCacheRead={String(debug.skipCacheRead)} · duration={debug.durationMs}ms · sources={debug.normalizedSourceCount}/{debug.sourceCount} |
| 168 | {debug.timedOutSources.length > 0 ? ` · timedOut=${debug.timedOutSources.join(",")}` : " · timedOut=none"} | 168 | {debug.timedOutSources.length > 0 ? ` · timedOut=${debug.timedOutSources.join(",")}` : " · timedOut=none"} |
| 169 | </p> | 169 | </p> |
| 170 | <p className="text-[10px] font-mono text-muted-foreground/80 truncate" title={debug.trace.join(" | ")}> | 170 | <p className="text-[10px] font-mono text-muted-foreground/80 truncate" title={debug.trace.join(" | ")}> |
| @@ -23,6 +23,7 @@ interface WorkspaceToolsQueryResult { · 22 unmodified lines | |||
| 23 | mode: "cache-fresh" | "cache-stale" | "rebuild"; | 23 | mode: "cache-fresh" | "cache-stale" | "rebuild"; |
| 24 | includeDts: boolean; | 24 | includeDts: boolean; |
| 25 | sourceTimeoutMs: number | null; | 25 | sourceTimeoutMs: number | null; |
| 26 | skipCacheRead: boolean; | ||
| 26 | sourceCount: number; | 27 | sourceCount: number; |
| 27 | normalizedSourceCount: number; | 28 | normalizedSourceCount: number; |
| 28 | cacheHit: boolean; | 29 | cacheHit: boolean; |
| @@ -96,6 +97,7 @@ export function useWorkspaceTools(context: WorkspaceContext | null) { · 67 unmodified lines | |||
| 96 | durationMs: inventoryData.debug.durationMs, | 97 | durationMs: inventoryData.debug.durationMs, |
| 97 | cacheHit: inventoryData.debug.cacheHit, | 98 | cacheHit: inventoryData.debug.cacheHit, |
| 98 | cacheFresh: inventoryData.debug.cacheFresh, | 99 | cacheFresh: inventoryData.debug.cacheFresh, |
| 100 | skipCacheRead: inventoryData.debug.skipCacheRead, | ||
| 99 | sourceCount: inventoryData.debug.sourceCount, | 101 | sourceCount: inventoryData.debug.sourceCount, |
| 100 | normalizedSourceCount: inventoryData.debug.normalizedSourceCount, | 102 | normalizedSourceCount: inventoryData.debug.normalizedSourceCount, |
| 101 | timedOutSources: inventoryData.debug.timedOutSources, | 103 | timedOutSources: inventoryData.debug.timedOutSources, |
| @@ -1,6 +1,8 @@ | |||
| 1 | #!/usr/bin/env bun | 1 | #!/usr/bin/env bun |
| 2 | 2 | ||
| 3 | import fs from "node:fs/promises"; | 3 | import fs from "node:fs/promises"; |
| 4 | import os from "node:os"; | ||
| 5 | import path from "node:path"; | ||
| 4 | import { managedRuntimeDiagnostics, runManagedBackend, runManagedWeb } from "./packages/core/src/managed-runtime"; | 6 | import { managedRuntimeDiagnostics, runManagedBackend, runManagedWeb } from "./packages/core/src/managed-runtime"; |
| 5 | 7 | ||
| 6 | function printHelp(): void { | 8 | function printHelp(): void { |
| @@ -11,12 +13,14 @@ Usage: · 4 unmodified lines | |||
| 11 | executor up [backend-args] | 13 | executor up [backend-args] |
| 12 | executor backend <args> | 14 | executor backend <args> |
| 13 | executor web [--port <number>] | 15 | executor web [--port <number>] |
| 16 | executor uninstall [--yes] | ||
| 14 | 17 | ||
| 15 | Commands: | 18 | Commands: |
| 16 | doctor Bootstrap and verify managed Convex backend runtime | 19 | doctor Bootstrap and verify managed Convex backend runtime |
| 17 | up Run managed backend and auto-bootstrap Convex functions | 20 | up Run managed backend and auto-bootstrap Convex functions |
| 18 | backend Pass through arguments to managed convex-local-backend binary | 21 | backend Pass through arguments to managed convex-local-backend binary |
| 19 | web Run packaged web UI (default port: 5312) | 22 | web Run packaged web UI (default port: 5312) |
| 23 | uninstall Remove local managed runtime install | ||
| 20 | `); | 24 | `); |
| 21 | } | 25 | } |
| 22 | 26 | ||
| @@ -61,6 +65,73 @@ async function checkHttp(url: string): Promise<boolean> { · 38 unmodified lines | |||
| 61 | } | 65 | } |
| 62 | } | 66 | } |
| 63 | 67 | ||
| 68 | async function runUninstall(args: string[]): Promise<number> { | ||
| 69 | let assumeYes = false; | ||
| 70 | let index = 0; | ||
| 71 | |||
| 72 | while (index < args.length) { | ||
| 73 | const arg = args[index]; | ||
| 74 | if (arg === "-y" || arg === "--yes") { | ||
| 75 | assumeYes = true; | ||
| 76 | index += 1; | ||
| 77 | continue; | ||
| 78 | } | ||
| 79 | |||
| 80 | if (arg === "-h" || arg === "--help") { | ||
| 81 | console.log(`Usage: | ||
| 82 | executor uninstall [--yes] | ||
| 83 | |||
| 84 | Options: | ||
| 85 | -y, --yes Skip confirmation prompt | ||
| 86 | -h, --help Show this help`); | ||
| 87 | return 0; | ||
| 88 | } | ||
| 89 | |||
| 90 | console.log(`Unknown option: ${arg}`); | ||
| 91 | return 1; | ||
| 92 | } | ||
| 93 | |||
| 94 | const installDir = Bun.env.EXECUTOR_INSTALL_DIR ?? path.join(os.homedir(), ".executor", "bin"); | ||
| 95 | const runtimeDir = Bun.env.EXECUTOR_RUNTIME_DIR ?? path.join(os.homedir(), ".executor", "runtime"); | ||
| 96 | const homeDir = Bun.env.EXECUTOR_HOME_DIR ?? path.join(os.homedir(), ".executor"); | ||
| 97 | |||
| 98 | if (!assumeYes) { | ||
| 99 | console.log("This will remove:"); | ||
| 100 | console.log(` - ${installDir}/executor`); | ||
| 101 | console.log(` - ${runtimeDir}`); | ||
| 102 | const response = prompt("Continue? [y/N] "); | ||
| 103 | if (response === null || response.toLowerCase() !== "y") { | ||
| 104 | console.log("Cancelled."); | ||
| 105 | return 0; | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | await fs.rm(path.join(installDir, "executor"), { force: true }); | ||
| 110 | await fs.rm(runtimeDir, { recursive: true, force: true }); | ||
| 111 | |||
| 112 | if (await pathExists(installDir)) { | ||
| 113 | try { | ||
| 114 | await fs.rmdir(installDir); | ||
| 115 | } catch { | ||
| 116 | // keep if it is not empty | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | if (await pathExists(homeDir)) { | ||
| 121 | try { | ||
| 122 | await fs.rmdir(homeDir); | ||
| 123 | } catch { | ||
| 124 | // keep if other files remain | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | console.log("Executor uninstall complete."); | ||
| 129 | console.log(""); | ||
| 130 | console.log("If you previously added PATH manually, remove this line from your shell rc:"); | ||
| 131 | console.log(` export PATH=${installDir}:$PATH`); | ||
| 132 | return 0; | ||
| 133 | } | ||
| 134 | |||
| 64 | async function run(): Promise<void> { | 135 | async function run(): Promise<void> { |
| 65 | const [command, ...rest] = process.argv.slice(2); | 136 | const [command, ...rest] = process.argv.slice(2); |
| 66 | 137 | ||
| @@ -110,6 +181,11 @@ async function run(): Promise<void> { · 43 unmodified lines | |||
| 110 | process.exit(exitCode); | 181 | process.exit(exitCode); |
| 111 | } | 182 | } |
| 112 | 183 | ||
| 184 | if (command === "uninstall") { | ||
| 185 | const exitCode = await runUninstall(rest); | ||
| 186 | process.exit(exitCode); | ||
| 187 | } | ||
| 188 | |||
| 113 | throw new Error(`Unknown command: ${command}`); | 189 | throw new Error(`Unknown command: ${command}`); |
| 114 | } | 190 | } |
| 115 | 191 | ||
| @@ -269,6 +269,7 @@ echo " executor up" · 268 unmodified lines | |||
| 269 | echo " executor web # defaults to http://localhost:5312" | 269 | echo " executor web # defaults to http://localhost:5312" |
| 270 | echo "" | 270 | echo "" |
| 271 | echo "To uninstall later:" | 271 | echo "To uninstall later:" |
| 272 | echo " executor uninstall --yes" | ||
| 272 | echo " bash executor/uninstall --yes" | 273 | echo " bash executor/uninstall --yes" |
| 273 | echo "" | 274 | echo "" |
| 274 | show_star_prompt | 275 | show_star_prompt |
| @@ -48,7 +48,7 @@ export const listToolsWithWarnings = action({ · 47 unmodified lines | |||
| 48 | workspaceId: args.workspaceId, | 48 | workspaceId: args.workspaceId, |
| 49 | actorId: canonicalActorId, | 49 | actorId: canonicalActorId, |
| 50 | clientId: args.clientId, | 50 | clientId: args.clientId, |
| 51 | }, { includeDts: false, sourceTimeoutMs: 2_500, allowStaleOnMismatch: true }); | 51 | }, { includeDts: false, sourceTimeoutMs: 2_500, allowStaleOnMismatch: true, skipCacheRead: true }); |
| 52 | 52 | ||
| 53 | if (inventory.warnings.some((warning) => warning.includes("showing previous results while refreshing"))) { | 53 | if (inventory.warnings.some((warning) => warning.includes("showing previous results while refreshing"))) { |
| 54 | try { | 54 | try { |
| @@ -39,6 +39,7 @@ export interface WorkspaceToolsDebug { · 38 unmodified lines | |||
| 39 | mode: "cache-fresh" | "cache-stale" | "rebuild"; | 39 | mode: "cache-fresh" | "cache-stale" | "rebuild"; |
| 40 | includeDts: boolean; | 40 | includeDts: boolean; |
| 41 | sourceTimeoutMs: number | null; | 41 | sourceTimeoutMs: number | null; |
| 42 | skipCacheRead: boolean; | ||
| 42 | sourceCount: number; | 43 | sourceCount: number; |
| 43 | normalizedSourceCount: number; | 44 | normalizedSourceCount: number; |
| 44 | cacheHit: boolean; | 45 | cacheHit: boolean; |
| @@ -52,6 +53,7 @@ interface GetWorkspaceToolsOptions { · 7 unmodified lines | |||
| 52 | includeDts?: boolean; | 53 | includeDts?: boolean; |
| 53 | sourceTimeoutMs?: number; | 54 | sourceTimeoutMs?: number; |
| 54 | allowStaleOnMismatch?: boolean; | 55 | allowStaleOnMismatch?: boolean; |
| 56 | skipCacheRead?: boolean; | ||
| 55 | } | 57 | } |
| 56 | 58 | ||
| 57 | export interface WorkspaceToolInventory { | 59 | export interface WorkspaceToolInventory { |
| @@ -133,6 +135,7 @@ export async function getWorkspaceTools( · 75 unmodified lines | |||
| 133 | const includeDts = options.includeDts ?? false; | 135 | const includeDts = options.includeDts ?? false; |
| 134 | const sourceTimeoutMs = options.sourceTimeoutMs; | 136 | const sourceTimeoutMs = options.sourceTimeoutMs; |
| 135 | const allowStaleOnMismatch = options.allowStaleOnMismatch ?? false; | 137 | const allowStaleOnMismatch = options.allowStaleOnMismatch ?? false; |
| 138 | const skipCacheRead = options.skipCacheRead ?? false; | ||
| 136 | const sources = (await ctx.runQuery(internal.database.listToolSources, { workspaceId })) | 139 | const sources = (await ctx.runQuery(internal.database.listToolSources, { workspaceId })) |
| 137 | .filter((source: { enabled: boolean }) => source.enabled); | 140 | .filter((source: { enabled: boolean }) => source.enabled); |
| 138 | traceStep("listToolSources", listSourcesStartedAt); | 141 | traceStep("listToolSources", listSourcesStartedAt); |
| @@ -141,10 +144,12 @@ export async function getWorkspaceTools( · 2 unmodified lines | |||
| 141 | const debugBase: Omit<WorkspaceToolsDebug, "mode" | "normalizedSourceCount" | "cacheHit" | "cacheFresh" | "timedOutSources" | "durationMs" | "trace"> = { | 144 | const debugBase: Omit<WorkspaceToolsDebug, "mode" | "normalizedSourceCount" | "cacheHit" | "cacheFresh" | "timedOutSources" | "durationMs" | "trace"> = { |
| 142 | includeDts, | 145 | includeDts, |
| 143 | sourceTimeoutMs: sourceTimeoutMs ?? null, | 146 | sourceTimeoutMs: sourceTimeoutMs ?? null, |
| 147 | skipCacheRead, | ||
| 144 | sourceCount: sources.length, | 148 | sourceCount: sources.length, |
| 145 | }; | 149 | }; |
| 146 | 150 | ||
| 147 | try { | 151 | if (!skipCacheRead) { |
| 152 | try { | ||
| 148 | const cacheReadStartedAt = Date.now(); | 153 | const cacheReadStartedAt = Date.now(); |
| 149 | const cacheEntry = await ctx.runQuery(internal.workspaceToolCache.getEntry, { | 154 | const cacheEntry = await ctx.runQuery(internal.workspaceToolCache.getEntry, { |
| 150 | workspaceId, | 155 | workspaceId, |
| @@ -202,9 +207,12 @@ export async function getWorkspaceTools( · 51 unmodified lines | |||
| 202 | } | 207 | } |
| 203 | } | 208 | } |
| 204 | } | 209 | } |
| 205 | } catch (error) { | 210 | } catch (error) { |
| 206 | const msg = error instanceof Error ? error.message : String(error); | 211 | const msg = error instanceof Error ? error.message : String(error); |
| 207 | console.warn(`[executor] workspace tool cache read failed for '${workspaceId}': ${msg}`); | 212 | console.warn(`[executor] workspace tool cache read failed for '${workspaceId}': ${msg}`); |
| 213 | } | ||
| 214 | } else { | ||
| 215 | trace.push("cacheEntryLookup=skipped"); | ||
| 208 | } | 216 | } |
| 209 | 217 | ||
| 210 | const configs: ExternalToolSourceConfig[] = []; | 218 | const configs: ExternalToolSourceConfig[] = []; |
| @@ -364,13 +372,24 @@ export async function getWorkspaceTools( · 153 unmodified lines | |||
| 364 | export async function loadWorkspaceToolInventoryForContext( | 372 | export async function loadWorkspaceToolInventoryForContext( |
| 365 | ctx: ActionCtx, | 373 | ctx: ActionCtx, |
| 366 | context: { workspaceId: Id<"workspaces">; actorId?: string; clientId?: string }, | 374 | context: { workspaceId: Id<"workspaces">; actorId?: string; clientId?: string }, |
| 367 | options: { includeDts?: boolean; sourceTimeoutMs?: number; allowStaleOnMismatch?: boolean } = {}, | 375 | options: { |
| 376 | includeDts?: boolean; | ||
| 377 | sourceTimeoutMs?: number; | ||
| 378 | allowStaleOnMismatch?: boolean; | ||
| 379 | skipCacheRead?: boolean; | ||
| 380 | } = {}, | ||
| 368 | ): Promise<WorkspaceToolInventory> { | 381 | ): Promise<WorkspaceToolInventory> { |
| 369 | const includeDts = options.includeDts ?? false; | 382 | const includeDts = options.includeDts ?? false; |
| 370 | const sourceTimeoutMs = options.sourceTimeoutMs; | 383 | const sourceTimeoutMs = options.sourceTimeoutMs; |
| 371 | const allowStaleOnMismatch = options.allowStaleOnMismatch; | 384 | const allowStaleOnMismatch = options.allowStaleOnMismatch; |
| 385 | const skipCacheRead = options.skipCacheRead; | ||
| 372 | const [result, policies] = await Promise.all([ | 386 | const [result, policies] = await Promise.all([ |
| 373 | getWorkspaceTools(ctx, context.workspaceId, { includeDts, sourceTimeoutMs, allowStaleOnMismatch }), | 387 | getWorkspaceTools(ctx, context.workspaceId, { |
| 388 | includeDts, | ||
| 389 | sourceTimeoutMs, | ||
| 390 | allowStaleOnMismatch, | ||
| 391 | skipCacheRead, | ||
| 392 | }), | ||
| 374 | ctx.runQuery(internal.database.listAccessPolicies, { workspaceId: context.workspaceId }), | 393 | ctx.runQuery(internal.database.listAccessPolicies, { workspaceId: context.workspaceId }), |
| 375 | ]); | 394 | ]); |
| 376 | const typedPolicies = policies as AccessPolicyRecord[]; | 395 | const typedPolicies = policies as AccessPolicyRecord[]; |
| @@ -405,7 +424,12 @@ export async function loadWorkspaceToolInventoryForContext( · 28 unmodified lines | |||
| 405 | export async function listToolsForContext( | 424 | export async function listToolsForContext( |
| 406 | ctx: ActionCtx, | 425 | ctx: ActionCtx, |
| 407 | context: { workspaceId: Id<"workspaces">; actorId?: string; clientId?: string }, | 426 | context: { workspaceId: Id<"workspaces">; actorId?: string; clientId?: string }, |
| 408 | options: { includeDts?: boolean; sourceTimeoutMs?: number; allowStaleOnMismatch?: boolean } = {}, | 427 | options: { |
| 428 | includeDts?: boolean; | ||
| 429 | sourceTimeoutMs?: number; | ||
| 430 | allowStaleOnMismatch?: boolean; | ||
| 431 | skipCacheRead?: boolean; | ||
| 432 | } = {}, | ||
| 409 | ): Promise<ToolDescriptor[]> { | 433 | ): Promise<ToolDescriptor[]> { |
| 410 | const inventory = await loadWorkspaceToolInventoryForContext(ctx, context, options); | 434 | const inventory = await loadWorkspaceToolInventoryForContext(ctx, context, options); |
| 411 | return inventory.tools; | 435 | return inventory.tools; |
| @@ -414,7 +438,12 @@ export async function listToolsForContext( · 2 unmodified lines | |||
| 414 | export async function listToolsWithWarningsForContext( | 438 | export async function listToolsWithWarningsForContext( |
| 415 | ctx: ActionCtx, | 439 | ctx: ActionCtx, |
| 416 | context: { workspaceId: Id<"workspaces">; actorId?: string; clientId?: string }, | 440 | context: { workspaceId: Id<"workspaces">; actorId?: string; clientId?: string }, |
| 417 | options: { includeDts?: boolean; sourceTimeoutMs?: number; allowStaleOnMismatch?: boolean } = {}, | 441 | options: { |
| 442 | includeDts?: boolean; | ||
| 443 | sourceTimeoutMs?: number; | ||
| 444 | allowStaleOnMismatch?: boolean; | ||
| 445 | skipCacheRead?: boolean; | ||
| 446 | } = {}, | ||
| 418 | ): Promise<{ | 447 | ): Promise<{ |
| 419 | tools: ToolDescriptor[]; | 448 | tools: ToolDescriptor[]; |
| 420 | warnings: string[]; | 449 | warnings: string[]; |
| @@ -33,6 +33,67 @@ async function generateSelfHostedAdminKey(info: ManagedRuntimeInfo): Promise<str · 32 unmodified lines | |||
| 33 | return parsed.adminKey; | 33 | return parsed.adminKey; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | async function hasAnyPath(paths: string[]): Promise<boolean> { | ||
| 37 | for (const candidate of paths) { | ||
| 38 | if (await pathExists(candidate)) { | ||
| 39 | return true; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | return false; | ||
| 43 | } | ||
| 44 | |||
| 45 | function getConfigCandidates(baseDir: string): string[] { | ||
| 46 | return [ | ||
| 47 | path.join(baseDir, "convex.config.ts"), | ||
| 48 | path.join(baseDir, "convex.config.js"), | ||
| 49 | path.join(baseDir, "convex.config.mts"), | ||
| 50 | path.join(baseDir, "convex.config.mjs"), | ||
| 51 | path.join(baseDir, "convex.config.cts"), | ||
| 52 | path.join(baseDir, "convex.config.cjs"), | ||
| 53 | ]; | ||
| 54 | } | ||
| 55 | |||
| 56 | async function getConfiguredFunctionsPath(candidate: string): Promise<string | null> { | ||
| 57 | try { | ||
| 58 | const raw = await fs.readFile(path.join(candidate, "convex.json"), "utf8"); | ||
| 59 | const parsed = JSON.parse(raw) as { functions?: unknown }; | ||
| 60 | if (typeof parsed.functions !== "string") { | ||
| 61 | return null; | ||
| 62 | } | ||
| 63 | const value = parsed.functions.trim(); | ||
| 64 | return value.length > 0 ? value : null; | ||
| 65 | } catch { | ||
| 66 | return null; | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | async function hasConvexProjectConfig(candidate: string): Promise<boolean> { | ||
| 71 | const convexJson = path.join(candidate, "convex.json"); | ||
| 72 | if (!(await pathExists(convexJson))) { | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | |||
| 76 | const legacyDir = path.join(candidate, "convex"); | ||
| 77 | const legacyMatches = await hasAnyPath(getConfigCandidates(legacyDir)); | ||
| 78 | if (legacyMatches) { | ||
| 79 | return true; | ||
| 80 | } | ||
| 81 | |||
| 82 | if (await hasAnyPath(getConfigCandidates(candidate))) { | ||
| 83 | return true; | ||
| 84 | } | ||
| 85 | |||
| 86 | const configuredFunctionsPath = await getConfiguredFunctionsPath(candidate); | ||
| 87 | if (!configuredFunctionsPath) { | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | const resolvedFunctionsPath = path.isAbsolute(configuredFunctionsPath) | ||
| 92 | ? configuredFunctionsPath | ||
| 93 | : path.resolve(candidate, configuredFunctionsPath); | ||
| 94 | return await hasAnyPath(getConfigCandidates(resolvedFunctionsPath)); | ||
| 95 | } | ||
| 96 | |||
| 36 | async function findProjectDir(): Promise<string | null> { | 97 | async function findProjectDir(): Promise<string | null> { |
| 37 | const roots = [ | 98 | const roots = [ |
| 38 | Bun.env.EXECUTOR_PROJECT_DIR, | 99 | Bun.env.EXECUTOR_PROJECT_DIR, |
| @@ -47,10 +108,7 @@ async function findProjectDir(): Promise<string | null> { · 8 unmodified lines | |||
| 47 | } | 108 | } |
| 48 | 109 | ||
| 49 | for (const candidate of candidates) { | 110 | for (const candidate of candidates) { |
| 50 | const convexDir = path.join(candidate, "convex"); | 111 | if (await hasConvexProjectConfig(candidate)) { |
| 51 | const convexConfig = path.join(convexDir, "convex.config.ts"); | ||
| 52 | const convexJson = path.join(candidate, "convex.json"); | ||
| 53 | if ((await pathExists(convexDir)) && (await pathExists(convexConfig)) && (await pathExists(convexJson))) { | ||
| 54 | return candidate; | 112 | return candidate; |
| 55 | } | 113 | } |
| 56 | } | 114 | } |
| @@ -74,11 +74,45 @@ export async function runManagedBackend(args: string[]): Promise<number> { · 73 unmodified lines | |||
| 74 | 74 | ||
| 75 | export async function runManagedWeb(options: { port?: number } = {}): Promise<number> { | 75 | export async function runManagedWeb(options: { port?: number } = {}): Promise<number> { |
| 76 | const info = await ensureManagedRuntime(); | 76 | const info = await ensureManagedRuntime(); |
| 77 | let managedBackend: ReturnType<typeof Bun.spawn> | null = null; | ||
| 78 | let startedManagedBackend = false; | ||
| 79 | |||
| 77 | try { | 80 | try { |
| 78 | await waitForBackendReady(info, 3_000); | 81 | try { |
| 79 | await ensureProjectBootstrapped(info); | 82 | await waitForBackendReady(info, 3_000); |
| 83 | } catch { | ||
| 84 | const backendEnv = { | ||
| 85 | ...process.env, | ||
| 86 | PATH: `${path.dirname(info.nodeBin)}:${process.env.PATH ?? ""}`, | ||
| 87 | }; | ||
| 88 | |||
| 89 | try { | ||
| 90 | managedBackend = Bun.spawn([info.backendBinary, ...backendArgs(info, [])], { | ||
| 91 | env: backendEnv, | ||
| 92 | stdin: "ignore", | ||
| 93 | stdout: "inherit", | ||
| 94 | stderr: "inherit", | ||
| 95 | }); | ||
| 96 | startedManagedBackend = true; | ||
| 97 | await waitForBackendReady(info); | ||
| 98 | } catch (error) { | ||
| 99 | const message = error instanceof Error ? error.message : String(error); | ||
| 100 | console.warn(`[executor] could not auto-start managed backend: ${message}`); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | if (startedManagedBackend) { | ||
| 105 | console.log("[executor] started managed Convex backend for web UI"); | ||
| 106 | } | ||
| 107 | |||
| 108 | try { | ||
| 109 | await ensureProjectBootstrapped(info); | ||
| 110 | } catch (error) { | ||
| 111 | const message = error instanceof Error ? error.message : String(error); | ||
| 112 | console.warn(`[executor] failed to auto-bootstrap Convex functions: ${message}`); | ||
| 113 | } | ||
| 80 | } catch { | 114 | } catch { |
| 81 | // Backend not running yet; web can still start and retry queries later. | 115 | // Backend unavailable for now; web can still start and retry queries later. |
| 82 | } | 116 | } |
| 83 | await ensureNodeRuntime(info); | 117 | await ensureNodeRuntime(info); |
| 84 | await ensureWebBundle(info); | 118 | await ensureWebBundle(info); |
| @@ -99,6 +133,12 @@ export async function runManagedWeb(options: { port?: number } = {}): Promise<nu · 14 unmodified lines | |||
| 99 | cwd: info.webDir, | 133 | cwd: info.webDir, |
| 100 | env, | 134 | env, |
| 101 | }); | 135 | }); |
| 136 | |||
| 137 | if (managedBackend && startedManagedBackend) { | ||
| 138 | managedBackend.kill(); | ||
| 139 | await managedBackend.exited; | ||
| 140 | } | ||
| 141 | |||
| 102 | return proc.exitCode; | 142 | return proc.exitCode; |
| 103 | } | 143 | } |
| 104 | 144 | ||