From c33d3cc02dbb93815cbdae73492d13e9dc7882c1 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Sun, 21 Jun 2026 21:48:03 -0500 Subject: [PATCH] Update stuff so we can pass build hopefully --- AGENTS.md | 8 +- apps/agent-worker/src/worker.ts | 105 ++------------------ package.json | 19 ++-- packages/backend/package.json | 1 + packages/backend/tests/unit/harness.test.ts | 28 ++---- scripts/build-next-app | 5 + scripts/convex-codegen | 52 ++++++++++ scripts/update-next-app | 1 + 8 files changed, 94 insertions(+), 125 deletions(-) create mode 100644 scripts/convex-codegen diff --git a/AGENTS.md b/AGENTS.md index 0d3ac97..e003d1a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,6 +23,8 @@ - Preserve `typescript.ignoreBuildErrors` in Next config. - Do not modify Sentry config or `tools/tailwind/theme.css` unless requested. - Generated `.cache`, `.turbo`, `.local`, and environment files are ignored. +- `scripts/convex-codegen` generates Convex API files for checks and image + builds when deployment env is available; do not hand-edit generated output. ## Environment rules @@ -36,6 +38,9 @@ variables from Infisical into the selected Convex deployment. Backend dev/setup scripts run it before `convex dev`. - CI uses Gitea-injected secrets or `CI_ENV_FILE` and must not call Infisical. +- CI must provide Convex deployment env for codegen, either + `CONVEX_SELF_HOSTED_URL` plus `CONVEX_SELF_HOSTED_ADMIN_KEY`, or + `CONVEX_DEPLOYMENT`. - App code imports validated variables from `@/env`, never `process.env`. - Add cache-relevant variables to `turbo.json` `globalEnv`. @@ -70,5 +75,6 @@ never connects to staging. ## Validation Use `bun typecheck`, never a production build for routine validation. The full -gate is `SKIP_E2E=1 bun run ci:check`; local-stack smoke e2e is `bun test:e2e`. +gate is `SKIP_E2E=1 bun run ci:check`; it runs Convex codegen before checking. +Local-stack smoke e2e is `bun test:e2e`. Pre-commit runs lint-staged serially and pre-push runs the bounded full gate. diff --git a/apps/agent-worker/src/worker.ts b/apps/agent-worker/src/worker.ts index b131a55..1bee109 100644 --- a/apps/agent-worker/src/worker.ts +++ b/apps/agent-worker/src/worker.ts @@ -1,7 +1,9 @@ import { access, readFile, rm } from 'node:fs/promises'; import path from 'node:path'; import { ConvexHttpClient } from 'convex/browser'; -import { makeFunctionReference } from 'convex/server'; + +import type { Id } from '@spoon/backend/convex/_generated/dataModel.js'; +import { api } from '@spoon/backend/convex/_generated/api.js'; import { runOpenAiEdit } from './agent'; import { env } from './env'; @@ -16,8 +18,6 @@ import { getInstallationToken, openDraftPullRequest } from './github'; import { createRedactor, truncate } from './redact'; import { runInJobContainer } from './runtime/docker'; -type Id = string & { __tableName: TableName }; - type Claim = { job: { _id: Id<'agentJobs'>; @@ -44,95 +44,6 @@ type Claim = { secrets: { name: string; value: string }[]; }; -const appendEventFunction = makeFunctionReference< - 'mutation', - { - workerToken: string; - workerId: string; - jobId: Id<'agentJobs'>; - level: 'debug' | 'info' | 'warn' | 'error'; - phase: - | 'queued' - | 'clone' - | 'plan' - | 'edit' - | 'install' - | 'check' - | 'test' - | 'commit' - | 'push' - | 'pr' - | 'cleanup'; - message: string; - metadata?: string; - }, - unknown ->('agentJobs:appendEvent'); - -const updateStatusFunction = makeFunctionReference< - 'mutation', - { - workerToken: string; - workerId: string; - jobId: Id<'agentJobs'>; - status: - | 'queued' - | 'claimed' - | 'preparing' - | 'running' - | 'checks_running' - | 'changes_ready' - | 'draft_pr_opened' - | 'failed' - | 'cancelled' - | 'timed_out'; - error?: string; - summary?: string; - }, - unknown ->('agentJobs:updateStatus'); - -const addArtifactFunction = makeFunctionReference< - 'mutation', - { - workerToken: string; - workerId: string; - jobId: Id<'agentJobs'>; - kind: 'plan' | 'diff' | 'test_output' | 'summary' | 'error' | 'pr_body'; - title: string; - content: string; - contentType: - | 'text/markdown' - | 'text/plain' - | 'application/json' - | 'text/x-diff'; - }, - unknown ->('agentJobs:addArtifact'); - -const completeWithDraftPrFunction = makeFunctionReference< - 'mutation', - { - workerToken: string; - workerId: string; - jobId: Id<'agentJobs'>; - commitSha: string; - pullRequestUrl: string; - pullRequestNumber: number; - summary: string; - }, - unknown ->('agentJobs:completeWithDraftPr'); - -const claimNextForWorkerFunction = makeFunctionReference< - 'action', - { - workerId: string; - workerToken: string; - }, - Claim | null ->('agentJobsNode:claimNextForWorker'); - const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const client = new ConvexHttpClient(env.convexUrl); @@ -155,7 +66,7 @@ const appendEvent = async ( message: string, metadata?: string, ) => - await client.mutation(appendEventFunction, { + await client.mutation(api.agentJobs.appendEvent, { workerToken: env.workerToken, workerId: env.workerId, jobId, @@ -180,7 +91,7 @@ const updateStatus = async ( | 'timed_out', extra?: { error?: string; summary?: string }, ) => - await client.mutation(updateStatusFunction, { + await client.mutation(api.agentJobs.updateStatus, { workerToken: env.workerToken, workerId: env.workerId, jobId, @@ -199,7 +110,7 @@ const addArtifact = async (args: { | 'application/json' | 'text/x-diff'; }) => - await client.mutation(addArtifactFunction, { + await client.mutation(api.agentJobs.addArtifact, { workerToken: env.workerToken, workerId: env.workerId, ...args, @@ -212,7 +123,7 @@ const completeWithDraftPr = async (args: { pullRequestNumber: number; summary: string; }) => - await client.mutation(completeWithDraftPrFunction, { + await client.mutation(api.agentJobs.completeWithDraftPr, { workerToken: env.workerToken, workerId: env.workerId, ...args, @@ -495,7 +406,7 @@ export const startWorker = async () => { console.log(`Spoon agent worker ${env.workerId} polling ${env.convexUrl}`); for (;;) { try { - const claim = await client.action(claimNextForWorkerFunction, { + const claim = await client.action(api.agentJobsNode.claimNextForWorker, { workerId: env.workerId, workerToken: env.workerToken, }); diff --git a/package.json b/package.json index 1c168d5..0643e4a 100644 --- a/package.json +++ b/package.json @@ -58,25 +58,26 @@ "dev:backend": "turbo run dev -F @spoon/backend", "dev:staging": "INFISICAL_ENV=staging turbo run dev -F @spoon/next -F @spoon/backend", "dev:expo:tunnel": "turbo run dev:tunnel -F @spoon/expo -F @spoon/backend", + "codegen:convex": "bash scripts/convex-codegen", "sync:convex": "scripts/sync-convex-env ${INFISICAL_ENV:-dev}", "db:up": "bash scripts/db/up", "db:down": "bash scripts/db/down", "db:down:wipe": "bash scripts/db/down --wipe", "format": "turbo run format --continue -- --cache --cache-location .cache/.prettiercache", "format:fix": "turbo run format --continue -- --write --cache --cache-location .cache/.prettiercache", - "lint": "turbo run lint --continue -- --cache --cache-location .cache/.eslintcache", - "lint:fix": "turbo run lint --continue -- --fix --cache --cache-location .cache/.eslintcache", + "lint": "bun codegen:convex && turbo run lint --continue -- --cache --cache-location .cache/.eslintcache", + "lint:fix": "bun codegen:convex && turbo run lint --continue -- --fix --cache --cache-location .cache/.eslintcache", "lint:ws": "bunx sherif@latest", "patch:usesend": "node scripts/patch-usesend.mjs", "postinstall": "bun patch:usesend && bun lint:ws", - "typecheck": "turbo run typecheck", - "test": "turbo run test:unit test:integration test:component", - "test:unit": "turbo run test:unit", - "test:integration": "turbo run test:integration", - "test:component": "turbo run test:component", + "typecheck": "bun codegen:convex && turbo run typecheck", + "test": "bun codegen:convex && turbo run test:unit test:integration test:component", + "test:unit": "bun codegen:convex && turbo run test:unit", + "test:integration": "bun codegen:convex && turbo run test:integration", + "test:component": "bun codegen:convex && turbo run test:component", "test:e2e": "bash scripts/e2e", - "test:all": "turbo run test:unit test:integration test:component && bun test:e2e", - "ci:check": "bun lint:ws && turbo run lint typecheck test:unit test:integration test:component --concurrency=2 && bun test:e2e", + "test:all": "bun codegen:convex && turbo run test:unit test:integration test:component && bun test:e2e", + "ci:check": "bun codegen:convex && bun lint:ws && turbo run lint typecheck test:unit test:integration test:component --concurrency=2 && bun test:e2e", "prepare": "husky", "ui-add": "turbo run ui-add", "android": "expo run:android", diff --git a/packages/backend/package.json b/packages/backend/package.json index ff15a67..c2aef3a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -17,6 +17,7 @@ "dev:tunnel": "bun sync-env && bun with-env convex dev", "dev:web": "bun sync-env && bun with-env convex dev", "setup": "bun sync-env && bun with-env convex dev --until-success", + "codegen": "convex codegen --typecheck disable", "clean": "git clean -xdf .cache .turbo dist node_modules", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint --flag unstable_native_nodejs_ts_config", diff --git a/packages/backend/tests/unit/harness.test.ts b/packages/backend/tests/unit/harness.test.ts index 01030cd..a8d4c43 100644 --- a/packages/backend/tests/unit/harness.test.ts +++ b/packages/backend/tests/unit/harness.test.ts @@ -1,17 +1,9 @@ import { convexTest } from 'convex-test'; -import { makeFunctionReference } from 'convex/server'; import { describe, expect, test } from 'vitest'; +import { api } from '../../convex/_generated/api.js'; import schema from '../../convex/schema'; -const createManualSpoon = makeFunctionReference<'mutation'>( - 'spoons:createManual', -); -const listMySpoons = makeFunctionReference<'query'>('spoons:listMine'); -const getSpoon = makeFunctionReference<'query'>('spoons:get'); -const createAgentRequest = makeFunctionReference<'mutation'>( - 'agentRequests:create', -); const modules = import.meta.glob('../../convex/**/*.*s'); const createUser = async (t: ReturnType, email: string) => @@ -49,9 +41,9 @@ describe('convex-test harness', () => { test('requires authentication to create a Spoon', async () => { const t = convexTest(schema, modules); - await expect(t.mutation(createManualSpoon, spoonInput)).rejects.toThrow( - 'Not authenticated.', - ); + await expect( + t.mutation(api.spoons.createManual, spoonInput), + ).rejects.toThrow('Not authenticated.'); }); test('creates and lists Spoons for the current user', async () => { @@ -59,8 +51,8 @@ describe('convex-test harness', () => { const userId = await createUser(t, 'one@example.com'); const session = authed(t, userId); - const spoonId = await session.mutation(createManualSpoon, spoonInput); - const spoons = await session.query(listMySpoons, {}); + const spoonId = await session.mutation(api.spoons.createManual, spoonInput); + const spoons = await session.query(api.spoons.listMine, {}); expect(spoons).toHaveLength(1); expect(spoons[0]?._id).toBe(spoonId); @@ -72,12 +64,12 @@ describe('convex-test harness', () => { const ownerId = await createUser(t, 'owner@example.com'); const otherId = await createUser(t, 'other@example.com'); const spoonId = await authed(t, ownerId).mutation( - createManualSpoon, + api.spoons.createManual, spoonInput, ); await expect( - authed(t, otherId).query(getSpoon, { spoonId }), + authed(t, otherId).query(api.spoons.get, { spoonId }), ).rejects.toThrow('Spoon not found.'); }); @@ -86,12 +78,12 @@ describe('convex-test harness', () => { const ownerId = await createUser(t, 'owner@example.com'); const otherId = await createUser(t, 'other@example.com'); const spoonId = await authed(t, ownerId).mutation( - createManualSpoon, + api.spoons.createManual, spoonInput, ); await expect( - authed(t, otherId).mutation(createAgentRequest, { + authed(t, otherId).mutation(api.agentRequests.create, { spoonId, prompt: 'Add a settings page', }), diff --git a/scripts/build-next-app b/scripts/build-next-app index 6e4b663..b8ccfb2 100755 --- a/scripts/build-next-app +++ b/scripts/build-next-app @@ -8,5 +8,10 @@ ENV_FILE="${CI_ENV_FILE:-}" cleanup() { [[ -n "$ENV_FILE" && "$ENV_FILE" != "${CI_ENV_FILE:-}" ]] && rm -f "$ENV_FILE" || true; } trap cleanup EXIT if [[ -z "$ENV_FILE" && -z "${CI:-}" ]]; then ENV_FILE="$(mktemp)"; sh "$ROOT_DIR/scripts/export-env" "$ENVIRONMENT" > "$ENV_FILE"; fi +if [[ -n "$ENV_FILE" ]]; then + bunx dotenv -e "$ENV_FILE" -- bash "$ROOT_DIR/scripts/convex-codegen" +else + bash "$ROOT_DIR/scripts/convex-codegen" +fi args=(); [[ -z "$ENV_FILE" ]] || args+=(--env-file "$ENV_FILE") docker compose "${args[@]}" -f "$ROOT_DIR/docker/compose.yml" build spoon-next diff --git a/scripts/convex-codegen b/scripts/convex-codegen new file mode 100644 index 0000000..fb5de2b --- /dev/null +++ b/scripts/convex-codegen @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +GENERATED_DIR="$ROOT_DIR/packages/backend/convex/_generated" + +if [[ -z "${CONVEX_SELF_HOSTED_URL:-}" ]]; then + if [[ -n "${CONVEX_URL:-}" ]]; then + export CONVEX_SELF_HOSTED_URL="$CONVEX_URL" + elif [[ -n "${NEXT_PUBLIC_CONVEX_URL:-}" ]]; then + export CONVEX_SELF_HOSTED_URL="$NEXT_PUBLIC_CONVEX_URL" + fi +fi + +has_generated_files() { + [[ -f "$GENERATED_DIR/api.js" ]] \ + && [[ -f "$GENERATED_DIR/api.d.ts" ]] \ + && [[ -f "$GENERATED_DIR/server.js" ]] \ + && [[ -f "$GENERATED_DIR/server.d.ts" ]] \ + && [[ -f "$GENERATED_DIR/dataModel.d.ts" ]] +} + +has_self_hosted_env() { + [[ -n "${CONVEX_SELF_HOSTED_URL:-}" ]] \ + && [[ -n "${CONVEX_SELF_HOSTED_ADMIN_KEY:-}" ]] +} + +has_cloud_deployment_env() { + [[ -n "${CONVEX_DEPLOYMENT:-}" ]] +} + +if has_self_hosted_env || has_cloud_deployment_env; then + cd "$ROOT_DIR/packages/backend" + bun run codegen + exit 0 +fi + +if has_generated_files; then + echo "Convex generated files already exist; skipping codegen because no deployment env is configured." + exit 0 +fi + +cat >&2 <<'EOF' +Convex generated files are missing. + +Run this command with either: + - CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY, or + - CONVEX_DEPLOYMENT + +CI should provide these through its env file before running checks. +EOF +exit 1 diff --git a/scripts/update-next-app b/scripts/update-next-app index 5d8a5f5..525e43c 100755 --- a/scripts/update-next-app +++ b/scripts/update-next-app @@ -5,5 +5,6 @@ ENVIRONMENT="${1:-staging}" [[ "$ENVIRONMENT" == dev || "$ENVIRONMENT" == staging ]] || { echo "usage: update-next-app [dev|staging]" >&2; exit 2; } ENV_FILE="$(mktemp)"; trap 'rm -f "$ENV_FILE"' EXIT sh "$ROOT_DIR/scripts/export-env" "$ENVIRONMENT" > "$ENV_FILE" +bunx dotenv -e "$ENV_FILE" -- bash "$ROOT_DIR/scripts/convex-codegen" docker compose --env-file "$ENV_FILE" -f "$ROOT_DIR/docker/compose.yml" build spoon-next docker compose --env-file "$ENV_FILE" -f "$ROOT_DIR/docker/compose.yml" up -d spoon-next