Update stuff so we can pass build hopefully
Build and Push Next App / quality (push) Failing after 39s
Build and Push Next App / build-next (push) Has been skipped

This commit is contained in:
Gabriel Brown
2026-06-21 21:48:03 -05:00
parent 97d29200d3
commit c33d3cc02d
8 changed files with 94 additions and 125 deletions
+7 -1
View File
@@ -23,6 +23,8 @@
- Preserve `typescript.ignoreBuildErrors` in Next config. - Preserve `typescript.ignoreBuildErrors` in Next config.
- Do not modify Sentry config or `tools/tailwind/theme.css` unless requested. - Do not modify Sentry config or `tools/tailwind/theme.css` unless requested.
- Generated `.cache`, `.turbo`, `.local`, and environment files are ignored. - 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 ## Environment rules
@@ -36,6 +38,9 @@
variables from Infisical into the selected Convex deployment. Backend variables from Infisical into the selected Convex deployment. Backend
dev/setup scripts run it before `convex dev`. dev/setup scripts run it before `convex dev`.
- CI uses Gitea-injected secrets or `CI_ENV_FILE` and must not call Infisical. - 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`. - App code imports validated variables from `@/env`, never `process.env`.
- Add cache-relevant variables to `turbo.json` `globalEnv`. - Add cache-relevant variables to `turbo.json` `globalEnv`.
@@ -70,5 +75,6 @@ never connects to staging.
## Validation ## Validation
Use `bun typecheck`, never a production build for routine validation. The full 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. Pre-commit runs lint-staged serially and pre-push runs the bounded full gate.
+8 -97
View File
@@ -1,7 +1,9 @@
import { access, readFile, rm } from 'node:fs/promises'; import { access, readFile, rm } from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { ConvexHttpClient } from 'convex/browser'; 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 { runOpenAiEdit } from './agent';
import { env } from './env'; import { env } from './env';
@@ -16,8 +18,6 @@ import { getInstallationToken, openDraftPullRequest } from './github';
import { createRedactor, truncate } from './redact'; import { createRedactor, truncate } from './redact';
import { runInJobContainer } from './runtime/docker'; import { runInJobContainer } from './runtime/docker';
type Id<TableName extends string> = string & { __tableName: TableName };
type Claim = { type Claim = {
job: { job: {
_id: Id<'agentJobs'>; _id: Id<'agentJobs'>;
@@ -44,95 +44,6 @@ type Claim = {
secrets: { name: string; value: string }[]; 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 sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const client = new ConvexHttpClient(env.convexUrl); const client = new ConvexHttpClient(env.convexUrl);
@@ -155,7 +66,7 @@ const appendEvent = async (
message: string, message: string,
metadata?: string, metadata?: string,
) => ) =>
await client.mutation(appendEventFunction, { await client.mutation(api.agentJobs.appendEvent, {
workerToken: env.workerToken, workerToken: env.workerToken,
workerId: env.workerId, workerId: env.workerId,
jobId, jobId,
@@ -180,7 +91,7 @@ const updateStatus = async (
| 'timed_out', | 'timed_out',
extra?: { error?: string; summary?: string }, extra?: { error?: string; summary?: string },
) => ) =>
await client.mutation(updateStatusFunction, { await client.mutation(api.agentJobs.updateStatus, {
workerToken: env.workerToken, workerToken: env.workerToken,
workerId: env.workerId, workerId: env.workerId,
jobId, jobId,
@@ -199,7 +110,7 @@ const addArtifact = async (args: {
| 'application/json' | 'application/json'
| 'text/x-diff'; | 'text/x-diff';
}) => }) =>
await client.mutation(addArtifactFunction, { await client.mutation(api.agentJobs.addArtifact, {
workerToken: env.workerToken, workerToken: env.workerToken,
workerId: env.workerId, workerId: env.workerId,
...args, ...args,
@@ -212,7 +123,7 @@ const completeWithDraftPr = async (args: {
pullRequestNumber: number; pullRequestNumber: number;
summary: string; summary: string;
}) => }) =>
await client.mutation(completeWithDraftPrFunction, { await client.mutation(api.agentJobs.completeWithDraftPr, {
workerToken: env.workerToken, workerToken: env.workerToken,
workerId: env.workerId, workerId: env.workerId,
...args, ...args,
@@ -495,7 +406,7 @@ export const startWorker = async () => {
console.log(`Spoon agent worker ${env.workerId} polling ${env.convexUrl}`); console.log(`Spoon agent worker ${env.workerId} polling ${env.convexUrl}`);
for (;;) { for (;;) {
try { try {
const claim = await client.action(claimNextForWorkerFunction, { const claim = await client.action(api.agentJobsNode.claimNextForWorker, {
workerId: env.workerId, workerId: env.workerId,
workerToken: env.workerToken, workerToken: env.workerToken,
}); });
+10 -9
View File
@@ -58,25 +58,26 @@
"dev:backend": "turbo run dev -F @spoon/backend", "dev:backend": "turbo run dev -F @spoon/backend",
"dev:staging": "INFISICAL_ENV=staging turbo run dev -F @spoon/next -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", "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}", "sync:convex": "scripts/sync-convex-env ${INFISICAL_ENV:-dev}",
"db:up": "bash scripts/db/up", "db:up": "bash scripts/db/up",
"db:down": "bash scripts/db/down", "db:down": "bash scripts/db/down",
"db:down:wipe": "bash scripts/db/down --wipe", "db:down:wipe": "bash scripts/db/down --wipe",
"format": "turbo run format --continue -- --cache --cache-location .cache/.prettiercache", "format": "turbo run format --continue -- --cache --cache-location .cache/.prettiercache",
"format:fix": "turbo run format --continue -- --write --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": "bun codegen:convex && turbo run lint --continue -- --cache --cache-location .cache/.eslintcache",
"lint:fix": "turbo run lint --continue -- --fix --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", "lint:ws": "bunx sherif@latest",
"patch:usesend": "node scripts/patch-usesend.mjs", "patch:usesend": "node scripts/patch-usesend.mjs",
"postinstall": "bun patch:usesend && bun lint:ws", "postinstall": "bun patch:usesend && bun lint:ws",
"typecheck": "turbo run typecheck", "typecheck": "bun codegen:convex && turbo run typecheck",
"test": "turbo run test:unit test:integration test:component", "test": "bun codegen:convex && turbo run test:unit test:integration test:component",
"test:unit": "turbo run test:unit", "test:unit": "bun codegen:convex && turbo run test:unit",
"test:integration": "turbo run test:integration", "test:integration": "bun codegen:convex && turbo run test:integration",
"test:component": "turbo run test:component", "test:component": "bun codegen:convex && turbo run test:component",
"test:e2e": "bash scripts/e2e", "test:e2e": "bash scripts/e2e",
"test:all": "turbo run test:unit test:integration test:component && bun test:e2e", "test:all": "bun codegen:convex && 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", "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", "prepare": "husky",
"ui-add": "turbo run ui-add", "ui-add": "turbo run ui-add",
"android": "expo run:android", "android": "expo run:android",
+1
View File
@@ -17,6 +17,7 @@
"dev:tunnel": "bun sync-env && bun with-env convex dev", "dev:tunnel": "bun sync-env && bun with-env convex dev",
"dev:web": "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", "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", "clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore", "format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint --flag unstable_native_nodejs_ts_config", "lint": "eslint --flag unstable_native_nodejs_ts_config",
+10 -18
View File
@@ -1,17 +1,9 @@
import { convexTest } from 'convex-test'; import { convexTest } from 'convex-test';
import { makeFunctionReference } from 'convex/server';
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import { api } from '../../convex/_generated/api.js';
import schema from '../../convex/schema'; 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 modules = import.meta.glob('../../convex/**/*.*s');
const createUser = async (t: ReturnType<typeof convexTest>, email: string) => const createUser = async (t: ReturnType<typeof convexTest>, email: string) =>
@@ -49,9 +41,9 @@ describe('convex-test harness', () => {
test('requires authentication to create a Spoon', async () => { test('requires authentication to create a Spoon', async () => {
const t = convexTest(schema, modules); const t = convexTest(schema, modules);
await expect(t.mutation(createManualSpoon, spoonInput)).rejects.toThrow( await expect(
'Not authenticated.', t.mutation(api.spoons.createManual, spoonInput),
); ).rejects.toThrow('Not authenticated.');
}); });
test('creates and lists Spoons for the current user', async () => { 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 userId = await createUser(t, 'one@example.com');
const session = authed(t, userId); const session = authed(t, userId);
const spoonId = await session.mutation(createManualSpoon, spoonInput); const spoonId = await session.mutation(api.spoons.createManual, spoonInput);
const spoons = await session.query(listMySpoons, {}); const spoons = await session.query(api.spoons.listMine, {});
expect(spoons).toHaveLength(1); expect(spoons).toHaveLength(1);
expect(spoons[0]?._id).toBe(spoonId); expect(spoons[0]?._id).toBe(spoonId);
@@ -72,12 +64,12 @@ describe('convex-test harness', () => {
const ownerId = await createUser(t, 'owner@example.com'); const ownerId = await createUser(t, 'owner@example.com');
const otherId = await createUser(t, 'other@example.com'); const otherId = await createUser(t, 'other@example.com');
const spoonId = await authed(t, ownerId).mutation( const spoonId = await authed(t, ownerId).mutation(
createManualSpoon, api.spoons.createManual,
spoonInput, spoonInput,
); );
await expect( await expect(
authed(t, otherId).query(getSpoon, { spoonId }), authed(t, otherId).query(api.spoons.get, { spoonId }),
).rejects.toThrow('Spoon not found.'); ).rejects.toThrow('Spoon not found.');
}); });
@@ -86,12 +78,12 @@ describe('convex-test harness', () => {
const ownerId = await createUser(t, 'owner@example.com'); const ownerId = await createUser(t, 'owner@example.com');
const otherId = await createUser(t, 'other@example.com'); const otherId = await createUser(t, 'other@example.com');
const spoonId = await authed(t, ownerId).mutation( const spoonId = await authed(t, ownerId).mutation(
createManualSpoon, api.spoons.createManual,
spoonInput, spoonInput,
); );
await expect( await expect(
authed(t, otherId).mutation(createAgentRequest, { authed(t, otherId).mutation(api.agentRequests.create, {
spoonId, spoonId,
prompt: 'Add a settings page', prompt: 'Add a settings page',
}), }),
+5
View File
@@ -8,5 +8,10 @@ ENV_FILE="${CI_ENV_FILE:-}"
cleanup() { [[ -n "$ENV_FILE" && "$ENV_FILE" != "${CI_ENV_FILE:-}" ]] && rm -f "$ENV_FILE" || true; } cleanup() { [[ -n "$ENV_FILE" && "$ENV_FILE" != "${CI_ENV_FILE:-}" ]] && rm -f "$ENV_FILE" || true; }
trap cleanup EXIT trap cleanup EXIT
if [[ -z "$ENV_FILE" && -z "${CI:-}" ]]; then ENV_FILE="$(mktemp)"; sh "$ROOT_DIR/scripts/export-env" "$ENVIRONMENT" > "$ENV_FILE"; fi 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") args=(); [[ -z "$ENV_FILE" ]] || args+=(--env-file "$ENV_FILE")
docker compose "${args[@]}" -f "$ROOT_DIR/docker/compose.yml" build spoon-next docker compose "${args[@]}" -f "$ROOT_DIR/docker/compose.yml" build spoon-next
+52
View File
@@ -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
+1
View File
@@ -5,5 +5,6 @@ ENVIRONMENT="${1:-staging}"
[[ "$ENVIRONMENT" == dev || "$ENVIRONMENT" == staging ]] || { echo "usage: update-next-app [dev|staging]" >&2; exit 2; } [[ "$ENVIRONMENT" == dev || "$ENVIRONMENT" == staging ]] || { echo "usage: update-next-app [dev|staging]" >&2; exit 2; }
ENV_FILE="$(mktemp)"; trap 'rm -f "$ENV_FILE"' EXIT ENV_FILE="$(mktemp)"; trap 'rm -f "$ENV_FILE"' EXIT
sh "$ROOT_DIR/scripts/export-env" "$ENVIRONMENT" > "$ENV_FILE" 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" build spoon-next
docker compose --env-file "$ENV_FILE" -f "$ROOT_DIR/docker/compose.yml" up -d spoon-next docker compose --env-file "$ENV_FILE" -f "$ROOT_DIR/docker/compose.yml" up -d spoon-next