History

bump version

Rhys Sullivan committed 9 months ago

Commit ee5eda11 parent b65ac9e9 files · +224 −18Browse files

Unified · Split

9 files
  1. executor/README.md +1 −1
  2. executor/apps/web/src/components/tools/view.tsx +1 −1
  3. executor/apps/web/src/hooks/use-workspace-tools.ts +2 −0
  4. executor/executor.ts +76 −0
  5. executor/install +1 −0
  6. executor/packages/convex/executorNode.ts +1 −1
  7. executor/packages/convex/runtime/workspace_tools.ts +37 −8
  8. executor/packages/core/src/managed-runtime-bootstrap.ts +62 −4
  9. executor/packages/core/src/managed-runtime.ts +43 −3
executor/README.md+1−1
@@ -83,7 +83,7 @@ executor web · 82 unmodified lines
8383 Uninstall:
8484
8585 ```bash
86−bash executor/uninstall --yes
86+executor uninstall --yes
8787 ```
8888
8989 Default managed-runtime ports:
executor/apps/web/src/components/tools/view.tsx+1−1
@@ -164,7 +164,7 @@ export function ToolsView({ · 163 unmodified lines
164164 {debug ? (
165165 <div className="space-y-0.5">
166166 <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}
168168 {debug.timedOutSources.length > 0 ? ` · timedOut=${debug.timedOutSources.join(",")}` : " · timedOut=none"}
169169 </p>
170170 <p className="text-[10px] font-mono text-muted-foreground/80 truncate" title={debug.trace.join(" | ")}>
executor/apps/web/src/hooks/use-workspace-tools.ts+2−0
@@ -23,6 +23,7 @@ interface WorkspaceToolsQueryResult { · 22 unmodified lines
2323 mode: "cache-fresh" | "cache-stale" | "rebuild";
2424 includeDts: boolean;
2525 sourceTimeoutMs: number | null;
26+ skipCacheRead: boolean;
2627 sourceCount: number;
2728 normalizedSourceCount: number;
2829 cacheHit: boolean;
@@ -96,6 +97,7 @@ export function useWorkspaceTools(context: WorkspaceContext | null) { · 67 unmodified lines
9697 durationMs: inventoryData.debug.durationMs,
9798 cacheHit: inventoryData.debug.cacheHit,
9899 cacheFresh: inventoryData.debug.cacheFresh,
100+ skipCacheRead: inventoryData.debug.skipCacheRead,
99101 sourceCount: inventoryData.debug.sourceCount,
100102 normalizedSourceCount: inventoryData.debug.normalizedSourceCount,
101103 timedOutSources: inventoryData.debug.timedOutSources,
executor/executor.ts+76−0
@@ -1,6 +1,8 @@
11 #!/usr/bin/env bun
22
33 import fs from "node:fs/promises";
4+import os from "node:os";
5+import path from "node:path";
46 import { managedRuntimeDiagnostics, runManagedBackend, runManagedWeb } from "./packages/core/src/managed-runtime";
57
68 function printHelp(): void {
@@ -11,12 +13,14 @@ Usage: · 4 unmodified lines
1113 executor up [backend-args]
1214 executor backend <args>
1315 executor web [--port <number>]
16+ executor uninstall [--yes]
1417
1518 Commands:
1619 doctor Bootstrap and verify managed Convex backend runtime
1720 up Run managed backend and auto-bootstrap Convex functions
1821 backend Pass through arguments to managed convex-local-backend binary
1922 web Run packaged web UI (default port: 5312)
23+ uninstall Remove local managed runtime install
2024 `);
2125 }
2226
@@ -61,6 +65,73 @@ async function checkHttp(url: string): Promise<boolean> { · 38 unmodified lines
6165 }
6266 }
6367
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+
64135 async function run(): Promise<void> {
65136 const [command, ...rest] = process.argv.slice(2);
66137
@@ -110,6 +181,11 @@ async function run(): Promise<void> { · 43 unmodified lines
110181 process.exit(exitCode);
111182 }
112183
184+ if (command === "uninstall") {
185+ const exitCode = await runUninstall(rest);
186+ process.exit(exitCode);
187+ }
188+
113189 throw new Error(`Unknown command: ${command}`);
114190 }
115191
executor/install+1−0
@@ -269,6 +269,7 @@ echo " executor up" · 268 unmodified lines
269269 echo " executor web # defaults to http://localhost:5312"
270270 echo ""
271271 echo "To uninstall later:"
272+echo " executor uninstall --yes"
272273 echo " bash executor/uninstall --yes"
273274 echo ""
274275 show_star_prompt
executor/packages/convex/executorNode.ts+1−1
@@ -48,7 +48,7 @@ export const listToolsWithWarnings = action({ · 47 unmodified lines
4848 workspaceId: args.workspaceId,
4949 actorId: canonicalActorId,
5050 clientId: args.clientId,
51− }, { includeDts: false, sourceTimeoutMs: 2_500, allowStaleOnMismatch: true });
51+ }, { includeDts: false, sourceTimeoutMs: 2_500, allowStaleOnMismatch: true, skipCacheRead: true });
5252
5353 if (inventory.warnings.some((warning) => warning.includes("showing previous results while refreshing"))) {
5454 try {
executor/packages/convex/runtime/workspace_tools.ts+37−8
@@ -39,6 +39,7 @@ export interface WorkspaceToolsDebug { · 38 unmodified lines
3939 mode: "cache-fresh" | "cache-stale" | "rebuild";
4040 includeDts: boolean;
4141 sourceTimeoutMs: number | null;
42+ skipCacheRead: boolean;
4243 sourceCount: number;
4344 normalizedSourceCount: number;
4445 cacheHit: boolean;
@@ -52,6 +53,7 @@ interface GetWorkspaceToolsOptions { · 7 unmodified lines
5253 includeDts?: boolean;
5354 sourceTimeoutMs?: number;
5455 allowStaleOnMismatch?: boolean;
56+ skipCacheRead?: boolean;
5557 }
5658
5759 export interface WorkspaceToolInventory {
@@ -133,6 +135,7 @@ export async function getWorkspaceTools( · 75 unmodified lines
133135 const includeDts = options.includeDts ?? false;
134136 const sourceTimeoutMs = options.sourceTimeoutMs;
135137 const allowStaleOnMismatch = options.allowStaleOnMismatch ?? false;
138+ const skipCacheRead = options.skipCacheRead ?? false;
136139 const sources = (await ctx.runQuery(internal.database.listToolSources, { workspaceId }))
137140 .filter((source: { enabled: boolean }) => source.enabled);
138141 traceStep("listToolSources", listSourcesStartedAt);
@@ -141,10 +144,12 @@ export async function getWorkspaceTools( · 2 unmodified lines
141144 const debugBase: Omit<WorkspaceToolsDebug, "mode" | "normalizedSourceCount" | "cacheHit" | "cacheFresh" | "timedOutSources" | "durationMs" | "trace"> = {
142145 includeDts,
143146 sourceTimeoutMs: sourceTimeoutMs ?? null,
147+ skipCacheRead,
144148 sourceCount: sources.length,
145149 };
146150
147− try {
151+ if (!skipCacheRead) {
152+ try {
148153 const cacheReadStartedAt = Date.now();
149154 const cacheEntry = await ctx.runQuery(internal.workspaceToolCache.getEntry, {
150155 workspaceId,
@@ -202,9 +207,12 @@ export async function getWorkspaceTools( · 51 unmodified lines
202207 }
203208 }
204209 }
205− } catch (error) {
206− const msg = error instanceof Error ? error.message : String(error);
207− console.warn(`[executor] workspace tool cache read failed for '${workspaceId}': ${msg}`);
210+ } catch (error) {
211+ const msg = error instanceof Error ? error.message : String(error);
212+ console.warn(`[executor] workspace tool cache read failed for '${workspaceId}': ${msg}`);
213+ }
214+ } else {
215+ trace.push("cacheEntryLookup=skipped");
208216 }
209217
210218 const configs: ExternalToolSourceConfig[] = [];
@@ -364,13 +372,24 @@ export async function getWorkspaceTools( · 153 unmodified lines
364372 export async function loadWorkspaceToolInventoryForContext(
365373 ctx: ActionCtx,
366374 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+ } = {},
368381 ): Promise<WorkspaceToolInventory> {
369382 const includeDts = options.includeDts ?? false;
370383 const sourceTimeoutMs = options.sourceTimeoutMs;
371384 const allowStaleOnMismatch = options.allowStaleOnMismatch;
385+ const skipCacheRead = options.skipCacheRead;
372386 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+ }),
374393 ctx.runQuery(internal.database.listAccessPolicies, { workspaceId: context.workspaceId }),
375394 ]);
376395 const typedPolicies = policies as AccessPolicyRecord[];
@@ -405,7 +424,12 @@ export async function loadWorkspaceToolInventoryForContext( · 28 unmodified lines
405424 export async function listToolsForContext(
406425 ctx: ActionCtx,
407426 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+ } = {},
409433 ): Promise<ToolDescriptor[]> {
410434 const inventory = await loadWorkspaceToolInventoryForContext(ctx, context, options);
411435 return inventory.tools;
@@ -414,7 +438,12 @@ export async function listToolsForContext( · 2 unmodified lines
414438 export async function listToolsWithWarningsForContext(
415439 ctx: ActionCtx,
416440 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+ } = {},
418447 ): Promise<{
419448 tools: ToolDescriptor[];
420449 warnings: string[];
executor/packages/core/src/managed-runtime-bootstrap.ts+62−4
@@ -33,6 +33,67 @@ async function generateSelfHostedAdminKey(info: ManagedRuntimeInfo): Promise<str · 32 unmodified lines
3333 return parsed.adminKey;
3434 }
3535
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+
3697 async function findProjectDir(): Promise<string | null> {
3798 const roots = [
3899 Bun.env.EXECUTOR_PROJECT_DIR,
@@ -47,10 +108,7 @@ async function findProjectDir(): Promise<string | null> { · 8 unmodified lines
47108 }
48109
49110 for (const candidate of candidates) {
50− const convexDir = path.join(candidate, "convex");
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))) {
111+ if (await hasConvexProjectConfig(candidate)) {
54112 return candidate;
55113 }
56114 }
executor/packages/core/src/managed-runtime.ts+43−3
@@ -74,11 +74,45 @@ export async function runManagedBackend(args: string[]): Promise<number> { · 73 unmodified lines
7474
7575 export async function runManagedWeb(options: { port?: number } = {}): Promise<number> {
7676 const info = await ensureManagedRuntime();
77+ let managedBackend: ReturnType<typeof Bun.spawn> | null = null;
78+ let startedManagedBackend = false;
79+
7780 try {
78− await waitForBackendReady(info, 3_000);
79− await ensureProjectBootstrapped(info);
81+ try {
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+ }
80114 } 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.
82116 }
83117 await ensureNodeRuntime(info);
84118 await ensureWebBundle(info);
@@ -99,6 +133,12 @@ export async function runManagedWeb(options: { port?: number } = {}): Promise<nu · 14 unmodified lines
99133 cwd: info.webDir,
100134 env,
101135 });
136+
137+ if (managedBackend && startedManagedBackend) {
138+ managedBackend.kill();
139+ await managedBackend.exited;
140+ }
141+
102142 return proc.exitCode;
103143 }
104144