Skip to content

Commit b3319df

Browse files
Use convex linter in deploy tool
remove some old bins fix some deps run stuff in parallel! WIP Add linter more
1 parent 2a3368c commit b3319df

30 files changed

+462
-210
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,5 @@ template/pnpm-lock.yaml
5050

5151
.vercel
5252
.env*.local
53+
54+
temp_envars

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ignore-workspace-root-check=true

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
pnpm-lock.yaml
22
.astro
33
proxy/proxy.bundled.cjs
4+
convex/_generated
5+
template

app/components/chat/Chat.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,13 @@ export const Chat = memo(
321321
},
322322
});
323323

324+
(window as any).chefMessages = messages;
325+
324326
// AKA "processed messages," since parsing has side effects
325327
const { parsedMessages, parseMessages } = useMessageParser(partCache);
326328

329+
(window as any).chefParsedMessages = parsedMessages;
330+
327331
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
328332

329333
useEffect(() => {

app/components/chat/ToolCall.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { deployToolParameters } from '~/lib/runtime/deployTool';
3232
import type { ZodError } from 'zod';
3333
import { Spinner } from '@ui/Spinner';
3434
import { FolderIcon } from '@heroicons/react/24/outline';
35+
import { getRelativePath } from '~/lib/stores/files';
36+
import { outputLabels } from '~/lib/runtime/deployToolOutputLabels';
3537

3638
export const ToolCall = memo(function ToolCall({ partId, toolCallId }: { partId: PartId; toolCallId: string }) {
3739
const userToggledAction = useRef(false);
@@ -370,7 +372,7 @@ function toolTitle(invocation: ConvexToolInvocation): React.ReactNode {
370372
extra = ` (lines ${start} - ${endName})`;
371373
}
372374
if (args.success) {
373-
renderedPath = args.data.path || '/home/project';
375+
renderedPath = getRelativePath(args.data.path) || '/home/project';
374376
}
375377
return (
376378
<div className="flex items-center gap-2">
@@ -405,10 +407,8 @@ function toolTitle(invocation: ConvexToolInvocation): React.ReactNode {
405407
);
406408
} else if (invocation.result?.startsWith('Error:')) {
407409
if (
408-
// This is a hack, but `npx convex dev` prints this out when the typecheck fails
409-
invocation.result.includes('To ignore failing typecheck') ||
410-
// this is a bigger hack! TypeScript fails with error codes like TS
411-
invocation.result.includes('Error TS')
410+
invocation.result.includes(`[${outputLabels.convexTypecheck}]`) ||
411+
invocation.result.includes(`[${outputLabels.frontendTypecheck}]`)
412412
) {
413413
return (
414414
<div className="flex items-center gap-2">

app/components/header/DeployButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function DeployButton() {
101101
const container = await webcontainer;
102102

103103
// Run the build command
104-
const buildProcess = await container.spawn('npx', ['vite', 'build', '--mode', 'development']);
104+
const buildProcess = await container.spawn('vite', ['build', '--mode', 'development']);
105105
const { output, exitCode } = await streamOutput(buildProcess);
106106
if (exitCode !== 0) {
107107
throw new Error(`Build failed: ${output}`);

app/lib/.server/chat.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ export async function chatAction({ request }: ActionFunctionArgs) {
123123

124124
try {
125125
const totalMessageContent = messages.reduce((acc, message) => acc + message.content, '');
126+
const totalMessagesContentPretty = messages.reduce((acc, message) => acc + '\n----\n' + message.content, '');
126127
logger.debug(`Total message length: ${totalMessageContent.split(' ').length}, words`);
128+
logger.debug(
129+
'last 1000 chars:',
130+
totalMessageContent.slice(Math.max(0, totalMessageContent.length - 1000), totalMessageContent.length),
131+
);
127132
const dataStream = await convexAgent(
128133
chatId,
129134
env,

app/lib/runtime/action-runner.ts

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { z } from 'zod';
1717
import { editToolParameters } from './editTool';
1818
import { getAbsolutePath } from '~/lib/stores/files';
1919
import { streamOutput } from '~/utils/process';
20+
import { outputLabels, type OutputLabels } from './deployToolOutputLabels';
2021

2122
const logger = createScopedLogger('ActionRunner');
2223

@@ -372,30 +373,94 @@ export class ActionRunner {
372373
case 'deploy': {
373374
const container = await this.#webcontainer;
374375
await waitForContainerBootState(ContainerBootState.READY);
375-
const convexProc = await container.spawn('sh', [
376-
'-c',
377-
'convex dev --once && tsc --noEmit -p tsconfig.app.json',
378-
]);
379-
action.abortSignal.addEventListener('abort', () => {
380-
convexProc.kill();
381-
});
382376

383-
const { output, exitCode } = await streamOutput(convexProc, {
384-
onOutput: (output) => {
377+
result = '';
378+
379+
const commandErroredController = new AbortController();
380+
const abortSignal = AbortSignal.any([action.abortSignal, commandErroredController.signal]);
381+
382+
/** Return a promise of output on success, throws an error containing output on failure. */
383+
const run = async (
384+
commandAndArgs: string[],
385+
errorPrefix: OutputLabels,
386+
onOutput?: (s: string) => void,
387+
): Promise<string> => {
388+
logger.info('starting to run', errorPrefix);
389+
const t0 = performance.now();
390+
const proc = await container.spawn(commandAndArgs[0], commandAndArgs.slice(1));
391+
const abortListener: () => void = () => proc.kill();
392+
abortSignal.addEventListener('abort', () => {
393+
logger.info('aborting', commandAndArgs);
394+
proc.kill();
395+
});
396+
const { output, exitCode } = await streamOutput(proc, { onOutput, debounceMs: 50 });
397+
398+
const cleanedOutput = cleanConvexOutput(output);
399+
const time = performance.now() - t0;
400+
logger.debug('finished', errorPrefix, 'in', Math.round(time));
401+
if (exitCode !== 0) {
402+
// Kill all other commands
403+
commandErroredController.abort(`${errorPrefix}`);
404+
// This command's output will be reported exclusively
405+
throw new Error(`[${errorPrefix}] Failed with exit code ${exitCode}: ${cleanedOutput}`);
406+
}
407+
abortSignal.removeEventListener('abort', abortListener);
408+
if (cleanedOutput.trim().length === 0) {
409+
return '';
410+
}
411+
return cleanedOutput + '\n\n';
412+
};
413+
414+
// START deploy tool call
415+
// / \
416+
// / \
417+
// codegen \ `convex typecheck` includes typecheck of convex/ dir
418+
// + typecheck \
419+
// | ESLint `eslint` must not include rules that check imports
420+
// | /
421+
// app typecheck / `tsc --noEmit --project tsconfig.app.json
422+
// \ /
423+
// \ /
424+
// deploy `deploy` can fail
425+
426+
// ESLint doesn't need to wait for codegen since we don't use rules like plugin-import to validate imports.
427+
const runEslint = async () => {
428+
if (await hasMatchingEslintConfig(container)) {
429+
// ESLint results don't stream to the terminal
430+
return await run(['eslint', 'convex'], outputLabels.convexLint);
431+
}
432+
return '';
433+
};
434+
435+
const runCodegenAndTypecheck = async (onOutput?: (output: string) => void) => {
436+
// Convex codegen does a convex directory typecheck, then tsc does a full-project typecheck.
437+
let output = await run(['convex', 'codegen'], outputLabels.frontendTypecheck, onOutput);
438+
output += await run(
439+
['tsc', '--noEmit', '-p', 'tsconfig.app.json'],
440+
outputLabels.frontendTypecheck,
441+
onOutput,
442+
);
443+
return output;
444+
};
445+
446+
const t0 = performance.now();
447+
const [eslintResult, codegenResult] = await Promise.all([
448+
runEslint(),
449+
runCodegenAndTypecheck((output) => {
450+
console.log('runing terminaloutput.set() with', output.length, 'characters');
385451
this.terminalOutput.set(output);
386-
},
387-
debounceMs: 50,
388-
});
389-
const cleanedOutput = cleanConvexOutput(output);
390-
if (exitCode !== 0) {
391-
throw new Error(`Convex failed with exit code ${exitCode}: ${cleanedOutput}`);
392-
}
393-
result = cleanedOutput;
452+
}),
453+
]);
454+
result += codegenResult;
455+
result += eslintResult;
456+
result += await run(['convex', 'dev', '--once', '--typecheck=disable'], outputLabels.convexDeploy);
457+
const time = performance.now() - t0;
458+
logger.info('deploy action finished in', time);
394459

395460
// Start the default preview if it’s not already running
396461
if (!workbenchStore.isDefaultPreviewRunning()) {
397462
const shell = this.#shellTerminal();
398-
await shell.startCommand('npx vite --open');
463+
await shell.startCommand('vite --open');
399464
result += '\n\nDev server started successfully!';
400465
}
401466

@@ -463,3 +528,16 @@ function cleanConvexOutput(output: string) {
463528
}
464529
return result;
465530
}
531+
532+
async function hasMatchingEslintConfig(container: WebContainer): Promise<boolean> {
533+
// Only run eslint if the file we expect is present and contains '@convex-dev/eslint-plugin'.
534+
let contents = '';
535+
try {
536+
contents = await container.fs.readFile('eslint.config.js', 'utf-8');
537+
} catch (e: any) {
538+
if (!e.message.includes('ENOENT: no such file or directory')) {
539+
throw e;
540+
}
541+
}
542+
return contents.includes('@convex-dev/eslint-plugin');
543+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const outputLabels = {
2+
convexCodegen: 'ConvexCodegen',
3+
convexTypecheck: 'ConvexTypecheck',
4+
frontendTypecheck: 'FrontendTypecheck',
5+
convexLint: 'ConvexLint',
6+
convexDeploy: 'ConvexDeploy',
7+
} as const;
8+
export type OutputLabels = (typeof outputLabels)[keyof typeof outputLabels];

app/lib/stores/startup/history.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
prepareMessageHistory,
1515
waitForNewMessages,
1616
} from './messages';
17+
import { createScopedLogger } from '~/utils/logger';
18+
19+
const logger = createScopedLogger('history');
1720

1821
const BACKUP_DEBOUNCE_MS = 1000;
1922

@@ -121,7 +124,7 @@ async function chatSyncWorker(args: { chatId: string; sessionId: Id<'sessions'>;
121124
const currentState = await waitForInitialized();
122125
const completeMessageInfo = lastCompleteMessageInfoStore.get();
123126
if (completeMessageInfo === null) {
124-
console.error('Complete message info not initialized');
127+
logger.error('Complete message info not initialized');
125128
continue;
126129
}
127130
const areMessagesUpToDate =
@@ -187,7 +190,7 @@ async function chatSyncWorker(args: { chatId: string; sessionId: Id<'sessions'>;
187190
);
188191
}
189192
if (messageBlob === undefined && snapshotBlob === undefined) {
190-
console.info('No updates to chat, skipping sync');
193+
logger.info('Complete message info not initialized');
191194
continue;
192195
}
193196
let response;
@@ -209,13 +212,13 @@ async function chatSyncWorker(args: { chatId: string; sessionId: Id<'sessions'>;
209212
}
210213
if (error !== null || (response !== undefined && !response.ok)) {
211214
const errorText = response !== undefined ? await response.text() : (error?.message ?? 'Unknown error');
212-
console.error('Failed to save chat:', errorText);
215+
logger.error('Complete message info not initialized');
213216
chatSyncState.set({
214217
...currentState,
215218
numFailures: currentState.numFailures + 1,
216219
});
217220
const sleepTime = backoffTime(currentState.numFailures);
218-
console.error(
221+
logger.error(
219222
`Failed to save chat (num failures: ${currentState.numFailures}), sleeping for ${sleepTime.toFixed(2)}ms`,
220223
errorText,
221224
);

0 commit comments

Comments
 (0)