From 5edb2f8bb6e55dfefdb52490aa9266e200da29d2 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Thu, 26 Jun 2025 10:54:39 -0700 Subject: [PATCH] Encrypt and store secret ENV vars in the repo with dotenvx (#114) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens Co-authored-by: John Richmond <5629+jr@users.noreply.github.com> --- .docker/Dockerfile.base | 31 -- .docker/Dockerfile.roomote-api | 40 +- .docker/Dockerfile.roomote-controller | 39 +- .docker/Dockerfile.roomote-dashboard | 38 +- .docker/Dockerfile.roomote-worker | 42 +- .dockerignore | 3 + .env | 0 .env.development | 24 + .env.preview | 22 + .env.production | 22 + .env.test | 21 + .github/workflows/CI.yml | 9 +- .gitignore | 5 +- .vscode/extensions.json | 3 +- apps/roomote-dashboard/package.json | 2 +- apps/roomote-dashboard/src/index.ts | 6 +- apps/roomote-dashboard/tsconfig.json | 2 +- apps/roomote/.env.example | 9 - apps/roomote/next.config.ts | 2 +- apps/roomote/package.json | 17 +- apps/roomote/src/app/api/health/route.ts | 26 +- apps/roomote/src/app/api/jobs/route.ts | 3 +- .../github/handlers/issueCommentHandler.ts | 2 +- .../webhooks/github/handlers/issueHandler.ts | 2 +- .../pullRequestReviewCommentHandler.ts | 2 +- .../app/api/webhooks/github/handlers/utils.ts | 8 +- apps/roomote/src/cli.ts | 311 ++++++++++++ apps/roomote/src/lib/controller.ts | 4 - apps/roomote/src/lib/job.ts | 18 +- apps/roomote/src/lib/jobs/fixGitHubIssue.ts | 3 +- apps/roomote/src/lib/jobs/index.ts | 3 + .../src/lib/jobs/processIssueComment.ts | 3 +- .../src/lib/jobs/processPullRequestComment.ts | 3 +- apps/roomote/src/lib/queue.ts | 2 +- apps/roomote/src/lib/runTask.ts | 40 +- apps/roomote/src/lib/slack.ts | 3 +- apps/roomote/src/lib/worker.ts | 8 +- apps/roomote/src/types/index.ts | 61 --- apps/web/.env | 27 +- apps/web/.env.development | 3 - apps/web/.env.production | 2 + apps/web/.env.test | 3 - apps/web/README.md | 4 +- apps/web/next.config.ts | 7 +- apps/web/package.json | 12 +- apps/web/src/actions/auth.ts | 4 +- apps/web/src/instrumentation.ts | 6 - .../lib/server/__tests__/agent-auth.test.ts | 34 +- apps/web/src/lib/server/agent-auth.ts | 12 +- apps/web/src/lib/server/analytics.ts | 2 +- apps/web/src/lib/server/env.ts | 47 -- apps/web/src/lib/server/index.ts | 1 - apps/web/src/lib/server/logger.ts | 13 +- docker-compose.yml | 18 +- fly.roomote-api.toml | 21 + fly.roomote-controller.toml | 16 + fly.roomote-dashboard.toml | 21 + package.json | 19 +- packages/db/.env.development | 3 - packages/db/.env.test | 3 - packages/db/drizzle.config.ts | 4 +- packages/db/package.json | 14 +- packages/db/src/db.ts | 17 +- packages/db/src/types.ts | 7 +- packages/env/eslint.config.mjs | 4 + packages/env/package.json | 23 + packages/env/src/__tests__/index.test.ts | 9 + packages/env/src/index.ts | 25 + packages/env/tsconfig.json | 8 + packages/env/vitest.config.ts | 9 + pnpm-lock.yaml | 446 ++++++------------ scripts/find-missing-translations.ts | 87 ---- scripts/test-agent-endpoint.ts | 221 --------- turbo.json | 6 + 74 files changed, 1032 insertions(+), 965 deletions(-) delete mode 100644 .docker/Dockerfile.base delete mode 100644 .env create mode 100644 .env.development create mode 100644 .env.preview create mode 100644 .env.production create mode 100644 .env.test delete mode 100644 apps/roomote/.env.example create mode 100644 apps/roomote/src/cli.ts create mode 100644 apps/roomote/src/lib/jobs/index.ts delete mode 100644 apps/roomote/src/types/index.ts delete mode 100644 apps/web/.env.development create mode 100644 apps/web/.env.production delete mode 100644 apps/web/.env.test delete mode 100644 apps/web/src/lib/server/env.ts create mode 100644 fly.roomote-api.toml create mode 100644 fly.roomote-controller.toml create mode 100644 fly.roomote-dashboard.toml delete mode 100644 packages/db/.env.development delete mode 100644 packages/db/.env.test create mode 100644 packages/env/eslint.config.mjs create mode 100644 packages/env/package.json create mode 100644 packages/env/src/__tests__/index.test.ts create mode 100644 packages/env/src/index.ts create mode 100644 packages/env/tsconfig.json create mode 100644 packages/env/vitest.config.ts delete mode 100644 scripts/find-missing-translations.ts delete mode 100644 scripts/test-agent-endpoint.ts diff --git a/.docker/Dockerfile.base b/.docker/Dockerfile.base deleted file mode 100644 index 3320b0b6a5..0000000000 --- a/.docker/Dockerfile.base +++ /dev/null @@ -1,31 +0,0 @@ -# docker compose build base - -FROM node:20-slim AS base - -# Install pnpm -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable - -# Install common system packages -RUN apt update && \ - apt install -y \ - curl \ - git \ - vim \ - jq \ - netcat-openbsd \ - apt-transport-https \ - ca-certificates \ - gnupg \ - lsb-release \ - wget \ - gpg \ - gh \ - && rm -rf /var/lib/apt/lists/* - -# Install Docker cli -RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ - && apt update && apt install -y docker-ce-cli \ - && rm -rf /var/lib/apt/lists/* diff --git a/.docker/Dockerfile.roomote-api b/.docker/Dockerfile.roomote-api index 55760939a5..00b39cfde9 100644 --- a/.docker/Dockerfile.roomote-api +++ b/.docker/Dockerfile.roomote-api @@ -1,12 +1,41 @@ -# docker compose build roomote-api +# docker compose build roomote-api && docker compose up roomote-api -FROM roo-code-cloud-base AS base +FROM node:20-slim AS base + +# Install pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +# Install common system packages +RUN apt update && \ + apt install -y \ + curl \ + git \ + vim \ + jq \ + netcat-openbsd \ + apt-transport-https \ + ca-certificates \ + gnupg \ + lsb-release \ + wget \ + gpg \ + gh \ + && rm -rf /var/lib/apt/lists/* + +# Install Docker cli +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt update && apt install -y docker-ce-cli \ + && rm -rf /var/lib/apt/lists/* WORKDIR /roo -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./ COPY packages/config-eslint/package.json ./packages/config-eslint/ COPY packages/config-typescript/package.json ./packages/config-typescript/ +COPY packages/env/package.json ./packages/env/ COPY packages/db/package.json ./packages/db/ COPY packages/ipc/package.json ./packages/ipc/ COPY apps/roomote/package.json ./apps/roomote/ @@ -15,13 +44,14 @@ RUN pnpm install COPY packages/config-eslint ./packages/config-eslint/ COPY packages/config-typescript ./packages/config-typescript/ +COPY packages/env ./packages/env/ COPY packages/db ./packages/db/ COPY packages/ipc ./packages/ipc/ COPY apps/roomote ./apps/roomote/ WORKDIR /roo/apps/roomote -RUN node_modules/.bin/next build +RUN npx dotenvx run -f ../../.env.${APP_ENV:-development} -- node_modules/.bin/next build EXPOSE 3001 ENV NODE_ENV=production -CMD ["node_modules/.bin/next", "start", "--port", "3001"] +CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- node_modules/.bin/next start --port 3001"] diff --git a/.docker/Dockerfile.roomote-controller b/.docker/Dockerfile.roomote-controller index bdff320283..95b533c768 100644 --- a/.docker/Dockerfile.roomote-controller +++ b/.docker/Dockerfile.roomote-controller @@ -1,12 +1,41 @@ -# docker compose build roomote-controller +# docker compose build roomote-controller && docker compose up roomote-controller -FROM roo-code-cloud-base AS base +FROM node:20-slim AS base + +# Install pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +# Install common system packages +RUN apt update && \ + apt install -y \ + curl \ + git \ + vim \ + jq \ + netcat-openbsd \ + apt-transport-https \ + ca-certificates \ + gnupg \ + lsb-release \ + wget \ + gpg \ + gh \ + && rm -rf /var/lib/apt/lists/* + +# Install Docker cli +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt update && apt install -y docker-ce-cli \ + && rm -rf /var/lib/apt/lists/* WORKDIR /roo -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./ COPY packages/config-eslint/package.json ./packages/config-eslint/ COPY packages/config-typescript/package.json ./packages/config-typescript/ +COPY packages/env/package.json ./packages/env/ COPY packages/db/package.json ./packages/db/ COPY packages/ipc/package.json ./packages/ipc/ COPY apps/roomote/package.json ./apps/roomote/ @@ -15,11 +44,13 @@ RUN pnpm install COPY packages/config-eslint ./packages/config-eslint/ COPY packages/config-typescript ./packages/config-typescript/ +COPY packages/env ./packages/env/ COPY packages/db ./packages/db/ COPY packages/ipc ./packages/ipc/ COPY apps/roomote ./apps/roomote/ WORKDIR /roo/apps/roomote + ENV NODE_ENV=production ENV HOST_EXECUTION_METHOD=docker -CMD ["pnpm", "controller"] +CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- tsx src/lib/controller.ts"] diff --git a/.docker/Dockerfile.roomote-dashboard b/.docker/Dockerfile.roomote-dashboard index fb65511448..73506cab2a 100644 --- a/.docker/Dockerfile.roomote-dashboard +++ b/.docker/Dockerfile.roomote-dashboard @@ -1,22 +1,52 @@ -# docker compose build roomote-dashboard +# docker compose build roomote-dashboard && docker compose up roomote-dashboard -FROM roo-code-cloud-base AS base +FROM node:20-slim AS base + +# Install pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +# Install common system packages +RUN apt update && \ + apt install -y \ + curl \ + git \ + vim \ + jq \ + netcat-openbsd \ + apt-transport-https \ + ca-certificates \ + gnupg \ + lsb-release \ + wget \ + gpg \ + gh \ + && rm -rf /var/lib/apt/lists/* + +# Install Docker cli +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt update && apt install -y docker-ce-cli \ + && rm -rf /var/lib/apt/lists/* WORKDIR /roo -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./ COPY packages/config-eslint/package.json ./packages/config-eslint/ COPY packages/config-typescript/package.json ./packages/config-typescript/ +COPY packages/env/package.json ./packages/env/ COPY apps/roomote-dashboard/package.json ./apps/roomote-dashboard/ RUN pnpm install COPY packages/config-eslint ./packages/config-eslint/ COPY packages/config-typescript ./packages/config-typescript/ +COPY packages/env ./packages/env/ COPY apps/roomote-dashboard ./apps/roomote-dashboard/ WORKDIR /roo/apps/roomote-dashboard EXPOSE 3002 ENV NODE_ENV=production -CMD ["pnpm", "dev"] +CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- tsx src/index.ts"] diff --git a/.docker/Dockerfile.roomote-worker b/.docker/Dockerfile.roomote-worker index 52a53b0c66..8e53c8e215 100644 --- a/.docker/Dockerfile.roomote-worker +++ b/.docker/Dockerfile.roomote-worker @@ -1,7 +1,34 @@ -# docker compose build roomote-worker -# Note: Requires $GH_TOKEN to be set as build argument. +# docker compose build roomote-worker && docker compose up roomote-worker -FROM roo-code-cloud-base AS base +FROM node:20-slim AS base + +# Install pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +# Install common system packages +RUN apt update && \ + apt install -y \ + curl \ + git \ + vim \ + jq \ + netcat-openbsd \ + apt-transport-https \ + ca-certificates \ + gnupg \ + lsb-release \ + wget \ + gpg \ + gh \ + && rm -rf /var/lib/apt/lists/* + +# Install Docker cli +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt update && apt install -y docker-ce-cli \ + && rm -rf /var/lib/apt/lists/* # Install additional worker-specific packages RUN apt update && \ @@ -39,11 +66,12 @@ WORKDIR /roo/repos/Roo-Code RUN gh repo set-default RooCodeInc/Roo-Code RUN pnpm install -# Install dependencies WORKDIR /roo -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./ COPY packages/config-eslint/package.json ./packages/config-eslint/ COPY packages/config-typescript/package.json ./packages/config-typescript/ +COPY packages/env/package.json ./packages/env/ COPY packages/db/package.json ./packages/db/ COPY packages/ipc/package.json ./packages/ipc/ COPY apps/roomote/package.json ./apps/roomote/ @@ -52,11 +80,13 @@ RUN pnpm install COPY packages/config-eslint ./packages/config-eslint/ COPY packages/config-typescript ./packages/config-typescript/ +COPY packages/env ./packages/env/ COPY packages/db ./packages/db/ COPY packages/ipc ./packages/ipc/ COPY apps/roomote ./apps/roomote/ WORKDIR /roo/apps/roomote + ENV NODE_ENV=production ENV HOST_EXECUTION_METHOD=docker -CMD ["pnpm", "worker"] +CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- tsx src/lib/controller.ts"] diff --git a/.dockerignore b/.dockerignore index a88b19ea61..ac9995d204 100644 --- a/.dockerignore +++ b/.dockerignore @@ -28,3 +28,6 @@ knip.json # next.js **/.next/ .vercel + +# env +.env.keys diff --git a/.env b/.env deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000000..4623173cda --- /dev/null +++ b/.env.development @@ -0,0 +1,24 @@ +#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ +#/ public-key encryption for .env files / +#/ [how it works](https://dotenvx.com/encryption) / +#/----------------------------------------------------------/ +DOTENV_PUBLIC_KEY_DEVELOPMENT="022703b50cc07889c89fc1425f6a780fc2cd813976eb5c9ed710687e550a0e1083" + +# .env.development +DATABASE_URL=encrypted:BHEnYqWD6ebRKQM5nSA9ETE9BERgb1KghegFptV61hZSoF267Iv1wXbJnMRSqvF8ZIMkr2FSlx3YhGRX+fmo82xEZdtiyy58PCVVHsPWaliX03TFqrEY8KyKRtkX8zbNccTf+rEtQ9Gq0TzGlhKp0fWe22IprYdMKJpmF8pJmFjCf91SbmpnUwuUuo93UI/dGOCUkNi+Uw== +REDIS_URL=encrypted:BOG9yPmE7Gx/5tqBwICkJGmsd/SyfsRhCtzFhZSz2D8bZdmR+AcJVoDlBSG2PPxbtSEPrm1Bv51vmN5pArO5+vm+Uu9IPaUzSA6/5IDGgw0MsFiSTcmkQq8KQHXyRArrm+bwSMbvyG9tzdNpRbgjkh6vvg== + +CLICKHOUSE_URL=encrypted:BJ9IPsZpsMW7rNYj89ZDfNAtpeAIIFmRcruWSgOTIsx6/ou3layaIwU/l5Nv5PEMqOWVZSWIU4+D6AEVprxCm9QzILWoBirEQRXBFMHAd5h0DgfRdDANB4/N6VyGKWJUPWLYbuOjs3qcJ54exM5mICapG0wgG/GlIEOyvAojuw== +CLICKHOUSE_PASSWORD=encrypted:BJ4Fdj7rqLD/7ElnUW2c5vuq/30mfXKTZBpxeUc1/kOSx5HzQZWgzSKam95l9pnP+EXuVgdofHwmsj2P9CN/+fm2OpdNwSG4yhFQkn27kj2nsaNiK/eyNchnprU0dlOnJUiYlcyENtAa + +CLERK_SECRET_KEY=encrypted:BOd1VAy8MDjf1xmeiN3/ez4TDwmSDgvpLkA27q+lIFlLAc953bwA4aUlU1FlDuoiVoouQJnn+1lLNHqkZ9l7ZInDB3fHGJYQYOHKY2563kOj24GBC5lNWRr2I8xU+Q2OuXSlTddYflHedqwZvhNOA9GCXd8kdxRqQ3xYRmxvj0XS1jUhvxmaBwFiGbZx+zd+Lspj + +GH_WEBHOOK_SECRET=encrypted:BG5ptaSJzmurbp1p1QTSl2Iyl6OQT5ONbjW4tbxQAdgUi+iOMHeFISIsRIFVCsLGKNDE9yOfV/NTzGfwwlG5s7xkvYffIIIRtZLr049ImgLHr14wXNrU+BQkU4QNHhA6P7GiQ+wfGocn28uJxtEirpSizhdSrfkusyYIt+iCTd2C +GH_TOKEN=encrypted:BGQvKArqcFk+y2UmBmzUZVBo6cNtDH20i0kHiqoGDRqAoFUJkc2hEumU6ukK+OSoATTLSAtRxfjwNLf9VTjHaGkRn4jExqNhvMCDLQqP8nTQ6NWvssvfb9e8RY4KAjTqXBBK1sCNfSWN647y+9pcTho4SMpmpb18clhuBkT0TlF7lyyckVE5aeM= + +OPENROUTER_API_KEY=encrypted:BA3bYeSEXFA0jnw3lmzLKz53ekkGr0K6sDLN6+nFGsa0TdJrYoKI5IcRPCMWuXIoLR1dlRi61eziWMQt0e2MgLU8zcR+zKif/2twlIzFlTU7OtFSwC1DPknxAG0pf3e2LSTDiLU4hRKgYbdJn8coA3rf/YY9NRVlmJV1gDLb9KVL89IWgT+fEn3Tzh383dSolr1ec/9RyAYVGpIYTg4O57OzQ9TaZp6InWs= + +SLACK_API_TOKEN=encrypted:BI5T2ZFY35FaOfSzPT8qQyUdhJs+fmL6wOFp8cVd4hM0xcSJ8va+vk1CCpltNrk25uXCd8D+fQnITb3G+L/1z1X8eqZVobqRwxO39wTcqVv1d9AQSLxAmYcEoAEWowiXQei2qS+AMcBSjZhDfURbdOfYo1+XHj0NdfspvsB1YwC+3CN3iZC/TSVBkzcQDy82Db541HnfFxhIQg== + + + diff --git a/.env.preview b/.env.preview new file mode 100644 index 0000000000..a8b5776404 --- /dev/null +++ b/.env.preview @@ -0,0 +1,22 @@ +#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ +#/ public-key encryption for .env files / +#/ [how it works](https://dotenvx.com/encryption) / +#/----------------------------------------------------------/ +DOTENV_PUBLIC_KEY_PREVIEW="03daea73b84e36000e1ecd63e339e6a49793b3212a48929896d0e7d8a8c94b0525" + +# .env.preview +DATABASE_URL=encrypted:BOJ23F02TQxTqP7t+e1ViPBO13fODlyM9Nhb0cuEQRHjc8cQmL8e5kU8+pGKSBD4Skh03cHp327GrM3cQLfu7F20zFIazrXv6dg4urA6umlgZJpyc+N8GodZ+BUCKWBf554gcouQAp/OJN0CZgOlboP3982dCxMfUKdx3nPZVVH7QoNmfk5tIwfHTUYqGMuN7KIdjUopVsaGvAxpqz7X53RkbnOpH2AAZDFzsdeLNV6/PIhjzuIh +REDIS_URL=encrypted:BOFDUP0rzhc/kIMljZ1QG4zOQROGFOApVU8kswfA/cJy09K103SS8V2t6wrWyWu3ZgwmBfNm3gQnrm8aP8ZNfFpTrm/V3azkeV+huzXLV81mkbu0zioO5f5OrPzvR5TVOz5+cQFVQdWzwGS/ZXDoLalmvIzPZ0u3nmFPRyMT9EJM8yGb59kF6E8OdfPFzO/Cw3K4xJtalufR2k+C5FZLC8AR6TFVUgQ+jlxSu66Zd5tAVGWk3QxAinn+kd41cqzENkKf0rBm1dG/DLZ00ggI + +CLICKHOUSE_URL=encrypted:BFauYbJKvW/D63ZYffeTG8EEmb7icwBJkKobendgtsn9C1mG7AkAhmcr23pJSRjjhFAiNfxZyn92mWXzZFBbulhTgFo461Ppz/y4oq/8bvLv1WKSZHG9E0BrQLx4QYHX+KymDX6pabvUftKpbQq3TnRPDjtdlYPEjxTTXCBYM7gjKsWCzp8pp6tXCrtfSqzbXB5xy0A2Rw== +CLICKHOUSE_PASSWORD=encrypted:BCZQ1MN8NoNoPVF3WxcIJLjSY5euH47/CVL4ESwAk0f6OZ6lstKFW4AaPrg5Wdd3uf9oPzcYM/dwawqhQ7bqYdtx76cBYbrtmHrirBWfvTlzX5knaV1E1xFE03jPvDGAAkK8RRU6UTQKC2lUq48= + +CLERK_SECRET_KEY=encrypted:BIJlh3p2P0zzTxgP9AwYarPeT3DsHs44M4fTP+ZQfDWwjtZw4QVbNMh2NBKbSOO/bH1XTVOFPZzy7N/NXIygmqeSJwdVLwAanb0eJKq2dNbwN0MAVsPTgL0U4t5A+9ytLRcdYeivoFzQ49wL1J6D38cdYSTKXQwcO3GDCRycylBZdOOzHbP8mE/y46d4RkuK7nay + +GH_WEBHOOK_SECRET=encrypted:BBVpX03MCeoEzrJbeP+bdWzB2bkDQ9VYMhZiRFckoBjN8foBb5oSI8YbyPKkRPCycfSTRirCddLzvNY/Q1Joe7pvynYubY0hW6YnbmVEaC6UYP27rGcWdJStMNfCI9OXvYaV765bjbmfBh59j+6FBTHzWuTS+xy84eRpcrzNiYjZ +GH_TOKEN=encrypted:BMB+HR14pdhTojK7exBUDaWpiQGDWziDR5cdUMM/I24MpkdCtZrvHsf+uTc/gTSnP6uGzOHc1xQAQG3OOGxA6IBls13Gv8wvVDOUdPm0NoyFSGMAqE7HbBi6GFSuNstuRNy92QJo86ZWH7La8llJXCNZ3juzFESD/rfAD1odhBkzBVDXfPdDonM= + +OPENROUTER_API_KEY=encrypted:BDoXCMz3fV5DXa89cwd77/pZhw6tVIw96LvtyZEFwFQtQV1Qk+ZmtQoObiKZkfI4y1ZvpOSL2ivXfS7YY8QtLnl8uEG550FHq6s0FZsVdua+SlmoUhJOK0zV341GadCG3bc5aSMB5Wha6ml51CJzy/AKud4U4Zi3eXaj+nMxeXF8wtnHsw51JQ3F7N9VnYklyrb5FEin2SVovg3TPZk40saP8Q/YyDyU8e8= + +SLACK_API_TOKEN=encrypted:BFmTkof1HoUg2UTgpm2wWdv5ZqVuldkgU5xLhGjgGFqRG7sOnAF5QdUVAfjV+890FbGF/BxBcb7bJsjMdf6+krq5U8p8Il2lucELdg1BoWisGcoy1+OCYoLxWm4RDrI/zovC09vaSnIdgIpz4T/4sNRbY5U8r7KaKXSCDPp2rCTUAC9Fc5cODlurbnMbfmPaFJIxKktqm5rfXg== + diff --git a/.env.production b/.env.production new file mode 100644 index 0000000000..0e416615c0 --- /dev/null +++ b/.env.production @@ -0,0 +1,22 @@ +#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ +#/ public-key encryption for .env files / +#/ [how it works](https://dotenvx.com/encryption) / +#/----------------------------------------------------------/ +DOTENV_PUBLIC_KEY_PRODUCTION="0220845c470bea52e1a524ffb098188cd9560b3610032586efdd7580cd3bf5798f" + +# .env.production +DATABASE_URL=encrypted:BMAmRVLIX9odtXaqSTZwduXFizp16qjO/vmXKfrsAlLW1jrkgWpltAl6AkVpACQehua2oopJPeTISffC+qRhEHOw/kkn94ePuMrldS9Z5V6XlbT6eXJJIRVO81S1yFqipzDj00m3HwlvxlT4y912MPAfwApdzx888Tu7Dix6UODVSyiqEHfXc0/WNKo3ly1NDjT/VjIxcIIqRCXMYkTc7thLBfY5KltajL+U4MOOpPLK7lzRDd1v +REDIS_URL=encrypted:BIsj6USwbqPSaFIoun8Gii1x/n2JTmpTEvMIKbN82noNRg0Wt1xJ8GEbrNB0UXxZ3Q7p/Ugf7zUu79MqsF2Nnkh5PkXxBJku0L4JNb2eoWZdiKHFaHVf5HAFG6yyIQtKVHWjS82BOA/QQMiZkgZ2VVYwB6zvrDNkIuI6tGCVdRkAm9grvPjgHDFc6ggd0o4OotBoK5mN0UdF5Jl+ILALNyZAsc8mvVJmtJSLvFWbjRe4bM14usNRGkq48ekuoyndt6g/3AzIvlUL4dsrd7TJosVWEpA= + +CLICKHOUSE_URL=encrypted:BNN8odgd5JZkV1lxYKejudbFTMClmNzerqnNTT1ALZxVH6en86Kn4kv2nb1ykFtp+NSED84BLBwrd2UdclIuaHpKreDtgVBnND34St2e+Coo52XJyvCFdgJ5E8KlDEPEukn6Ju+/oJORzhEUBUclPnrtQjOwIufEjojt9jZQ8tq6Hf/Y5c6CNgY+OeMjq5tjf6EX5uIs4g== +CLICKHOUSE_PASSWORD=encrypted:BGDdYw0Se+gkPbdpqzbrWq40r+B4s1iW4CGS61Ybq8eZzKs+b9KjD3fBqI3Z8n5mETSLheNFaYsanFj+1IzsdOz0t8YpapXmZAZyCDWLBmXrh49vlf+hhfrgdC1L/JO4H5wL/d01g+iDuuwJZz0= + +CLERK_SECRET_KEY=encrypted:BDV4gzZvpKtTMWjY/5eTjuq99u13Ukr941xK4kvMvmKNEvf18znpa8vXDS9VZhKG/Od1Sp1axCv5YEDq7x9u3YE+hcS5NrldE6uOqbTwzO6HLYvHdiT/PKDiEmUmnW8p4GrS+MqaNlY9JNie/TSUzebLzvN5SzWIZfiiLXoHLFuTtrvoJ9NU2GZWjtSwXT0ndCkW + +GH_WEBHOOK_SECRET=encrypted:BAoBNKhYwOuEAVJTBdEhmM2E+P9EiJJO1Zuf6v3xkDziWVIgTkjy0HPgN3+v4HauJCcQSzRBojd99QoyhTARmNGM55V3S9ErBWO/aS1rVJbWhRq25xHGBZU/r36a5kbpYA9t8u4oQ1O0b0E9U/xGmukqqrbWAZmi6GLjkcFiARAQ +GH_TOKEN=encrypted:BN93qO9v+VfEZFaIdRwyhwR2CPM0rw1QUE9SSm/qjIT6W9XnL4Gg6EStB9zfldMIA4oD+MEQqsfllXCIcpjNHfUlbeFeM+Ma9a1lhP5oD4B5pKlZW+qVk3Ne5Yr/c0okgyzGUp2RzWENIyDvxubMevBczZ6Uo36yjqlvhUcRYhfRB2wWx7Y4I8Q= + +OPENROUTER_API_KEY=encrypted:BLBANC6AI/i5gHIeiBaY5lOogEEahxSLAvsxUpk6Z1FXtfrIEj/6EKA/CE1LzHOJEZNIXHTtlb41ONGQWuRFtgDcvbhSthZ5XZB4c9dcdPz0cxEU4RfWSRwjQxYZ8OMs1oLdr7NV8oR6IYwJ43lVfuePBYit7zX125Mp/25R7Kl50oSVREHnlfhtOMTOTQqLI7LutgGvQiDym1Ji1BFDaTBd4rTrkoymYxo= + +SLACK_API_TOKEN=encrypted:BHrN/c76f9fFC6XlzfgRnrb2NXK1jlGrq5XbSqk0FM+t4BaLFGILW4RruQRnbBVVm4dGNVCP395H0rHXAQsFd25nPYbBUXiHY42Ap2jBfy3xVqm3dWhapffv4+MiTMdEJvpVY+yyxJwNAu2RFEEnI5I4UpogpY7yMFvHyzV2Jym3/9pJ1svQpCPqAzz79SXRLDQq8w1b7LmQhA== + diff --git a/.env.test b/.env.test new file mode 100644 index 0000000000..2ada575edb --- /dev/null +++ b/.env.test @@ -0,0 +1,21 @@ +#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ +#/ public-key encryption for .env files / +#/ [how it works](https://dotenvx.com/encryption) / +#/----------------------------------------------------------/ +DOTENV_PUBLIC_KEY_TEST="02852f7005fb38145aa0d7a0a356158ba31dffeb6cb0280aa9671ee9a7673ddc94" + +# .env.test +DATABASE_URL=encrypted:BBzcyQFl877jbvtrWEoYrvA+AX1xRsVPyjQwlL/5yUyFsVFN/CXVbHFooQPzesG46YemJNkYzWGebMDWNZNbPc09rAROipuDwOvipkb6MS9E2IRUuQqUDVtnvlCuDD0VK+7492RpKfrFB73owT83EiQw/XJt+FMFSIW+FK09fE3mIDAOIeNdfBPwZoOiaAeVTQ== +REDIS_URL=encrypted:BFF5ijYrLHtSlP9lxZVKsXB6RLV2dXJFzFxpk3+mVp0aA8r2Ez7g6nW7PWivmDxJjlCLg1nA27pR/Gzezg4GLXbqwDY9zV+e7Z7P46QHVvToRKDmGAuOV4CsKGTURHjLVof4K156CIu0ltyEUy7bhJuXtYqgoik= + +CLICKHOUSE_URL=encrypted:BH8OZoeYgQo7Gc0MgfTSM03qQR6qdR6/cx8t25vHOv9+SOLnddFcle/WbtnzXvUsiLGNO3/IKlwx5WRBqzfDwSnbnPend+1kRZT1GO4qZp+JXWGY7bqQExplzA+YUrMj2NQEqPg3xORK7EkHTRLJ0CZYghKSAw== +CLICKHOUSE_PASSWORD=encrypted:BKa4hWylvhPLPG+MpqIdAfyNAYZY1DzxMVHxIqb294W9Ec/V7TV2Reb68mZIKsFHJ7931cbEX7rgq5qIHHedJzW9t43GsX6k2SIdF4LFMIvYBEVhdRk4Yeu1aQpZ+pxpqtAyLMEOl/Mg + +CLERK_SECRET_KEY=encrypted:BGDuYROAkWIQ6nS75BFIqFlNm1nuCU8o9AOODPSs3+yxoLv8X3G0UGDI5V8xFxvN0lNgK2FBTG3GqkbEOeVZAQHXEzEJGsIQZ7Yb+/WMw6PR6CQEuvX/oKqlrNiLsyhS2St4DICSJERdR8foHA== + +GH_WEBHOOK_SECRET=encrypted:BPqZwnjqQE1EXxEZhAlM5lHSMseL3blLLB2XsK50/1k77AUTtRBWHY7V/1KN5CvYocieNVury1L7t8zuxyr4vk0ZIcZZsvsLYzWu/4CiIieHc1/oh0HqjG6ZCl86EwCscPEVBgQ= +GH_TOKEN=encrypted:BGefstTA/5VjgS/e9iy7TCvM64YaKDDgp3SSr6IUGZfWrfqAhyMZe5VmxyNlNEV/UeodnnNEzT+mMtwq1HG1PWDnxl9gepXyJfulWIIx2x7SzaUfChQFXCjLKQ/AwZunnZ1HmjSreD1J + +OPENROUTER_API_KEY=encrypted:BJHyYNaUsHiU6By54yUA2lG48W6NFuafGOr6Sc4XDopmTB9WulF1LbWrP0hiHY+GcWNhb3JR6V4XSOoqQDjNPza65uJCVOob1bWdC1vVoLBjxLev19OUI53jCi0kQVuNYz7P7zI5/YTWk0noSDU= + +SLACK_API_TOKEN=encrypted:BDkskSYEb9SYQLpS0LyyOMgS7pVOyha9agMrS15iQUBoeULTzXbfGxEc+w3oEUa7mFyuRW+gHpo1jqEYicc2Nm9xXawDFsfhoTgXkhUdDlAVsJY/jctWtw97Hb9qLvigRrJWyx7ha5wUMg== diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cc401439b8..dc06346b6a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,10 +5,13 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: env: NODE_VERSION: 20.19.2 PNPM_VERSION: 10.12.1 + DOTENV_PRIVATE_KEY_TEST: ${{ secrets.DOTENV_PRIVATE_KEY_TEST }} + DOTENV_PRIVATE_KEY_DEVELOPMENT: ${{ secrets.DOTENV_PRIVATE_KEY_DEVELOPMENT }} jobs: build: @@ -31,6 +34,8 @@ jobs: run: pnpm install - name: Build workspaces run: pnpm build + env: + DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} test: runs-on: ubuntu-latest @@ -132,7 +137,7 @@ jobs: - name: Install dependencies run: pnpm install - name: Migrate production database - run: npx drizzle-kit migrate + run: pnpm db:production:migrate working-directory: packages/db env: - DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }} + DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} diff --git a/.gitignore b/.gitignore index cb32b4f5ad..d384f5d289 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,8 @@ node_modules Thumbs.db # .env -.env*.local -.env*.preview -.env*.production +.env.keys +.env.local # turbo .turbo diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d68ed9b8b0..8440989115 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "csstools.postcss", "bradlc.vscode-tailwindcss", "vitest.explorer", - "yoavbls.pretty-ts-errors" + "yoavbls.pretty-ts-errors", + "tamasfe.even-better-toml" ] } diff --git a/apps/roomote-dashboard/package.json b/apps/roomote-dashboard/package.json index 607a319c9e..a376218606 100644 --- a/apps/roomote-dashboard/package.json +++ b/apps/roomote-dashboard/package.json @@ -5,7 +5,7 @@ "scripts": { "lint": "eslint src --ext=ts --max-warnings=0", "check-types": "tsc --noEmit", - "dev": "tsx src/index.ts", + "dev": "dotenvx run -f ../../.env.development -- tsx src/index.ts", "clean": "rimraf .turbo" }, "dependencies": { diff --git a/apps/roomote-dashboard/src/index.ts b/apps/roomote-dashboard/src/index.ts index 3b122d77f7..be054e1477 100644 --- a/apps/roomote-dashboard/src/index.ts +++ b/apps/roomote-dashboard/src/index.ts @@ -6,7 +6,11 @@ import { createBullBoard } from '@bull-board/api'; import express from 'express'; import type { Express, Request, Response } from 'express'; -const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6380', { +if (!process.env.REDIS_URL) { + throw new Error('REDIS_URL is not set'); +} + +const redis = new Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null, }); diff --git a/apps/roomote-dashboard/tsconfig.json b/apps/roomote-dashboard/tsconfig.json index 231e1b06d8..54fa32a48d 100644 --- a/apps/roomote-dashboard/tsconfig.json +++ b/apps/roomote-dashboard/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "@roo-code-cloud/config-typescript/base.json", - "include": ["src/**/*.ts"], + "include": ["src"], "exclude": ["node_modules"] } diff --git a/apps/roomote/.env.example b/apps/roomote/.env.example deleted file mode 100644 index 8f086cc85d..0000000000 --- a/apps/roomote/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -DATABASE_URL=postgres://postgres:password@postgres:5432/development -REDIS_URL=redis://localhost:6379 - -GH_WEBHOOK_SECRET=your-webhook-secret-here -GH_TOKEN=your-token-here - -OPENROUTER_API_KEY=sk-or-v1-... - -SLACK_API_TOKEN=xoxb-... diff --git a/apps/roomote/next.config.ts b/apps/roomote/next.config.ts index 9d3c27b2e8..85e2abbf73 100644 --- a/apps/roomote/next.config.ts +++ b/apps/roomote/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { - serverExternalPackages: ['postgres', 'ioredis', 'bullmq'], + serverExternalPackages: ['ioredis', 'bullmq'], }; export default nextConfig; diff --git a/apps/roomote/package.json b/apps/roomote/package.json index da0d7f0d6f..f428b58a25 100644 --- a/apps/roomote/package.json +++ b/apps/roomote/package.json @@ -5,19 +5,22 @@ "scripts": { "lint": "next lint --max-warnings 0", "check-types": "tsc --noEmit", - "test": "vitest", - "dev": "next dev --port 3001", - "build": "next build", - "start": "next start --port 3001", - "clean": "rimraf .next .turbo", - "worker": "tsx src/lib/worker.ts", - "controller": "tsx src/lib/controller.ts" + "test": "dotenvx run -f ../../.env.test -- vitest", + "dev": "dotenvx run -f ../../.env.development -- next dev --turbopack --port 3001", + "build": "dotenvx run -f ../../.env.production -- next build", + "start": "dotenvx run -f ../../.env.production -- next start --port 3001", + "worker": "dotenvx run -f ../../.env.development -- tsx src/lib/worker.ts", + "controller": "dotenvx run -f ../../.env.development -- tsx src/lib/controller.ts", + "cli": "dotenvx run -f ../../.env.development -- tsx src/cli.ts", + "clean": "rimraf .next .turbo" }, "dependencies": { "@roo-code-cloud/db": "workspace:^", + "@roo-code-cloud/env": "workspace:^", "@roo-code-cloud/ipc": "workspace:^", "@roo-code/types": "^1.29.0", "bullmq": "^5.37.0", + "cmd-ts": "^0.13.0", "drizzle-orm": "^0.44.2", "execa": "^9.6.0", "ioredis": "^5.6.1", diff --git a/apps/roomote/src/app/api/health/route.ts b/apps/roomote/src/app/api/health/route.ts index f30d2acfe1..018a40be73 100644 --- a/apps/roomote/src/app/api/health/route.ts +++ b/apps/roomote/src/app/api/health/route.ts @@ -1,12 +1,31 @@ import { NextResponse } from 'next/server'; import { db } from '@roo-code-cloud/db/server'; +import { Env } from '@roo-code-cloud/env'; import { redis } from '@/lib'; export async function GET() { try { const services = { database: false, redis: false }; + const hosts = { database: 'unknown', redis: 'unknown' }; + + try { + const databaseUrl = new URL(Env.DATABASE_URL); + hosts.database = databaseUrl.hostname; + } catch (error) { + console.error('Failed to parse DATABASE_URL:', error); + } + + try { + const redisUrl = new URL( + process.env.REDIS_URL || 'redis://localhost:6379', + ); + + hosts.redis = redisUrl.hostname; + } catch (error) { + console.error('Failed to parse REDIS_URL:', error); + } try { await db.execute('SELECT 1'); @@ -22,10 +41,11 @@ export async function GET() { console.error('Redis health check failed:', error); } - const allHealthy = Object.values(services).every(Boolean); + const isHealthy = Object.values(services).every(Boolean); + return NextResponse.json( - { status: allHealthy ? 'ok' : 'error', services }, - { status: allHealthy ? 200 : 500 }, + { status: isHealthy ? 'ok' : 'error', services, hosts }, + { status: isHealthy ? 200 : 500 }, ); } catch (error) { console.error('Health check error:', error); diff --git a/apps/roomote/src/app/api/jobs/route.ts b/apps/roomote/src/app/api/jobs/route.ts index eb759d12fa..3e4e5430e1 100644 --- a/apps/roomote/src/app/api/jobs/route.ts +++ b/apps/roomote/src/app/api/jobs/route.ts @@ -1,9 +1,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; -import { db, cloudJobs } from '@roo-code-cloud/db/server'; +import { createJobSchema, db, cloudJobs } from '@roo-code-cloud/db/server'; -import { createJobSchema } from '@/types'; import { enqueue } from '@/lib'; export async function POST(request: NextRequest) { diff --git a/apps/roomote/src/app/api/webhooks/github/handlers/issueCommentHandler.ts b/apps/roomote/src/app/api/webhooks/github/handlers/issueCommentHandler.ts index 08e4c440b2..93e81fc0a4 100644 --- a/apps/roomote/src/app/api/webhooks/github/handlers/issueCommentHandler.ts +++ b/apps/roomote/src/app/api/webhooks/github/handlers/issueCommentHandler.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { z } from 'zod'; -import type { JobPayload } from '@/types'; +import type { JobPayload } from '@roo-code-cloud/db'; import { createAndEnqueueJob } from './utils'; diff --git a/apps/roomote/src/app/api/webhooks/github/handlers/issueHandler.ts b/apps/roomote/src/app/api/webhooks/github/handlers/issueHandler.ts index 6e58795d87..e322e85683 100644 --- a/apps/roomote/src/app/api/webhooks/github/handlers/issueHandler.ts +++ b/apps/roomote/src/app/api/webhooks/github/handlers/issueHandler.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { z } from 'zod'; -import type { JobPayload } from '@/types'; +import type { JobPayload } from '@roo-code-cloud/db'; import { createAndEnqueueJob } from './utils'; diff --git a/apps/roomote/src/app/api/webhooks/github/handlers/pullRequestReviewCommentHandler.ts b/apps/roomote/src/app/api/webhooks/github/handlers/pullRequestReviewCommentHandler.ts index bd83cfd092..23ad0a92ea 100644 --- a/apps/roomote/src/app/api/webhooks/github/handlers/pullRequestReviewCommentHandler.ts +++ b/apps/roomote/src/app/api/webhooks/github/handlers/pullRequestReviewCommentHandler.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { z } from 'zod'; -import type { JobPayload } from '@/types'; +import type { JobPayload } from '@roo-code-cloud/db'; import { createAndEnqueueJob } from './utils'; diff --git a/apps/roomote/src/app/api/webhooks/github/handlers/utils.ts b/apps/roomote/src/app/api/webhooks/github/handlers/utils.ts index 3e198f11a4..2edd6a3bda 100644 --- a/apps/roomote/src/app/api/webhooks/github/handlers/utils.ts +++ b/apps/roomote/src/app/api/webhooks/github/handlers/utils.ts @@ -1,8 +1,12 @@ import { createHmac } from 'crypto'; -import { db, cloudJobs } from '@roo-code-cloud/db/server'; +import { + type JobType, + type JobPayload, + db, + cloudJobs, +} from '@roo-code-cloud/db/server'; -import type { JobType, JobPayload } from '@/types'; import { enqueue } from '@/lib'; export function verifySignature( diff --git a/apps/roomote/src/cli.ts b/apps/roomote/src/cli.ts new file mode 100644 index 0000000000..a64c70bfff --- /dev/null +++ b/apps/roomote/src/cli.ts @@ -0,0 +1,311 @@ +import * as path from 'path'; +import * as os from 'node:os'; + +import { + command, + run, + string, + number, + option, + subcommands, + optional, +} from 'cmd-ts'; + +import type { JobPayload } from '@roo-code-cloud/db'; + +import { + fixGitHubIssue, + processIssueComment, + processPullRequestComment, +} from '@/lib/jobs'; +import { runTask } from '@/lib/runTask'; +import { Logger } from '@/lib/logger'; + +const logger = new Logger({ + logDir: path.resolve(os.tmpdir(), 'logs'), + filename: 'cli.log', + tag: 'worker', +}); + +const fixIssueCommand = command({ + name: 'fix-issue', + description: 'Fix a GitHub issue', + args: { + repo: option({ + type: string, + long: 'repo', + short: 'r', + description: 'Repository name (e.g., owner/repo)', + }), + issue: option({ + type: number, + long: 'issue', + short: 'i', + description: 'Issue number', + }), + title: option({ + type: string, + long: 'title', + short: 't', + description: 'Issue title', + }), + body: option({ + type: string, + long: 'body', + short: 'b', + description: 'Issue body/description', + }), + labels: option({ + type: optional(string), + long: 'labels', + short: 'l', + description: 'Issue labels (comma-separated: -l "bug,enhancement")', + }), + }, + handler: async ({ repo, issue, title, body, labels }) => { + try { + console.log('๐Ÿ”ง Fixing GitHub issue...'); + + const payload: JobPayload<'github.issue.fix'> = { + repo, + issue, + title, + body, + labels: labels ? labels.split(',').map((l) => l.trim()) : [], + }; + + const result = await fixGitHubIssue(payload); + + console.log('โœ… Issue fix completed successfully!'); + console.log(`Repository: ${result.repo}`); + console.log(`Issue: #${result.issue}`); + console.log('Result:', result.result); + } catch (error) { + console.error('โŒ Error fixing issue:', error); + process.exit(1); + } + }, +}); + +const respondIssueCommentCommand = command({ + name: 'respond-issue-comment', + description: 'Respond to a GitHub issue comment', + args: { + repo: option({ + type: string, + long: 'repo', + short: 'r', + description: 'Repository name (e.g., owner/repo)', + }), + issueNumber: option({ + type: number, + long: 'issue-number', + description: 'Issue number', + }), + issueTitle: option({ + type: string, + long: 'issue-title', + description: 'Issue title', + }), + issueBody: option({ + type: string, + long: 'issue-body', + description: 'Issue body', + }), + commentId: option({ + type: number, + long: 'comment-id', + description: 'Comment ID', + }), + commentBody: option({ + type: string, + long: 'comment-body', + description: 'Comment body', + }), + commentAuthor: option({ + type: string, + long: 'comment-author', + description: 'Comment author', + }), + commentUrl: option({ + type: string, + long: 'comment-url', + description: 'Comment URL', + }), + }, + handler: async (args) => { + try { + console.log('๐Ÿ’ฌ Responding to GitHub issue comment...'); + + const payload: JobPayload<'github.issue.comment.respond'> = { + repo: args.repo, + issueNumber: args.issueNumber, + issueTitle: args.issueTitle, + issueBody: args.issueBody, + commentId: args.commentId, + commentBody: args.commentBody, + commentAuthor: args.commentAuthor, + commentUrl: args.commentUrl, + }; + + const result = await processIssueComment(payload); + + console.log('โœ… Issue comment response completed successfully!'); + console.log('Result:', result); + } catch (error) { + console.error('โŒ Error responding to issue comment:', error); + process.exit(1); + } + }, +}); + +const respondPrCommentCommand = command({ + name: 'respond-pr-comment', + description: 'Respond to a GitHub PR comment', + args: { + repo: option({ + type: string, + long: 'repo', + short: 'r', + description: 'Repository name (e.g., owner/repo)', + }), + prNumber: option({ + type: number, + long: 'pr-number', + description: 'PR number', + }), + prTitle: option({ + type: string, + long: 'pr-title', + description: 'PR title', + }), + prBody: option({ + type: string, + long: 'pr-body', + description: 'PR body', + }), + prBranch: option({ + type: string, + long: 'pr-branch', + description: 'PR branch', + }), + baseRef: option({ + type: string, + long: 'base-ref', + description: 'Base reference', + }), + commentId: option({ + type: number, + long: 'comment-id', + description: 'Comment ID', + }), + commentBody: option({ + type: string, + long: 'comment-body', + description: 'Comment body', + }), + commentAuthor: option({ + type: string, + long: 'comment-author', + description: 'Comment author', + }), + commentType: option({ + type: string, + long: 'comment-type', + description: 'Comment type (issue_comment or review_comment)', + }), + commentUrl: option({ + type: string, + long: 'comment-url', + description: 'Comment URL', + }), + }, + handler: async (args) => { + try { + console.log('๐Ÿ’ฌ Responding to GitHub PR comment...'); + + if ( + args.commentType !== 'issue_comment' && + args.commentType !== 'review_comment' + ) { + throw new Error( + 'Comment type must be either "issue_comment" or "review_comment"', + ); + } + + const payload: JobPayload<'github.pr.comment.respond'> = { + repo: args.repo, + prNumber: args.prNumber, + prTitle: args.prTitle, + prBody: args.prBody, + prBranch: args.prBranch, + baseRef: args.baseRef, + commentId: args.commentId, + commentBody: args.commentBody, + commentAuthor: args.commentAuthor, + commentType: args.commentType as 'issue_comment' | 'review_comment', + commentUrl: args.commentUrl, + }; + + const result = await processPullRequestComment(payload); + + console.log('โœ… PR comment response completed successfully!'); + console.log('Result:', result); + } catch (error) { + console.error('โŒ Error responding to PR comment:', error); + process.exit(1); + } + }, +}); + +const promptCommand = command({ + name: 'prompt', + description: 'Prompt', + args: { + text: option({ + type: string, + long: 'text', + description: 'Text to test', + }), + mode: option({ + type: string, + long: 'mode', + description: 'Mode to use: code, ask, architect', + }), + workspacePath: option({ + type: string, + long: 'workspace-path', + description: 'Workspace path', + }), + }, + handler: async ({ text, mode, workspacePath }) => { + if (!['code', 'ask', 'architect'].includes(mode)) { + throw new Error('Invalid mode'); + } + + console.log(`${text} (${mode}) in ${workspacePath}`); + + await runTask({ + jobType: 'test.prompt', + jobPayload: { text }, + prompt: text, + logger, + notify: false, + workspacePath, + settings: { mode }, + }); + }, +}); + +const app = subcommands({ + name: 'roomote-cli', + description: 'Roomote CLI - Run jobs directly from the command line', + cmds: { + 'fix-issue': fixIssueCommand, + 'respond-issue-comment': respondIssueCommentCommand, + 'respond-pr-comment': respondPrCommentCommand, + prompt: promptCommand, + }, +}); + +run(app, process.argv.slice(2)); diff --git a/apps/roomote/src/lib/controller.ts b/apps/roomote/src/lib/controller.ts index 12bd78e457..adfcefc5d8 100644 --- a/apps/roomote/src/lib/controller.ts +++ b/apps/roomote/src/lib/controller.ts @@ -81,11 +81,7 @@ export class WorkerController { `--name roomote-${workerId}`, '--rm', '--network roo-code-cloud_default', - '-e HOST_EXECUTION_METHOD=docker', `-e GH_TOKEN=${process.env.GH_TOKEN}`, - `-e DATABASE_URL=${process.env.DATABASE_URL}`, - `-e REDIS_URL=${process.env.REDIS_URL}`, - `-e NODE_ENV=${process.env.NODE_ENV}`, '-v /var/run/docker.sock:/var/run/docker.sock', '-v /tmp/roomote:/var/log/roomote', ]; diff --git a/apps/roomote/src/lib/job.ts b/apps/roomote/src/lib/job.ts index fab31f3c02..dcd1d7a92c 100644 --- a/apps/roomote/src/lib/job.ts +++ b/apps/roomote/src/lib/job.ts @@ -1,9 +1,15 @@ import { eq } from 'drizzle-orm'; import { Job } from 'bullmq'; -import { db, cloudJobs, type UpdateCloudJob } from '@roo-code-cloud/db/server'; - -import type { JobType, JobStatus, JobParams, JobPayload } from '@/types'; +import { + type JobType, + type JobStatus, + type JobParams, + type JobPayload, + type UpdateCloudJob, + db, + cloudJobs, +} from '@roo-code-cloud/db/server'; import { fixGitHubIssue } from './jobs/fixGitHubIssue'; import { processPullRequestComment } from './jobs/processPullRequestComment'; @@ -26,7 +32,7 @@ export async function processJob({ payload as JobPayload<'github.issue.fix'>, { onTaskStarted: async ( - slackThreadTs: string | null, + slackThreadTs: string | null | undefined, _rooTaskId: string, ) => { if (slackThreadTs) { @@ -48,7 +54,7 @@ export async function processJob({ payload as JobPayload<'github.issue.comment.respond'>, { onTaskStarted: async ( - slackThreadTs: string | null, + slackThreadTs: string | null | undefined, _rooTaskId: string, ) => { if (slackThreadTs) { @@ -70,7 +76,7 @@ export async function processJob({ payload as JobPayload<'github.pr.comment.respond'>, { onTaskStarted: async ( - slackThreadTs: string | null, + slackThreadTs: string | null | undefined, _rooTaskId: string, ) => { if (slackThreadTs) { diff --git a/apps/roomote/src/lib/jobs/fixGitHubIssue.ts b/apps/roomote/src/lib/jobs/fixGitHubIssue.ts index 49dfc3f1b5..616a04d367 100644 --- a/apps/roomote/src/lib/jobs/fixGitHubIssue.ts +++ b/apps/roomote/src/lib/jobs/fixGitHubIssue.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as os from 'node:os'; -import type { JobType, JobPayload } from '@/types'; +import type { JobType, JobPayload } from '@roo-code-cloud/db'; import { runTask, type RunTaskCallbacks } from '../runTask'; import { Logger } from '../logger'; @@ -46,7 +46,6 @@ Your job isn't done until you've created a pull request. Try to solve any git is jobType, jobPayload, prompt, - publish: async () => {}, logger: new Logger({ logDir: path.resolve(os.tmpdir(), 'logs'), filename: 'worker.log', diff --git a/apps/roomote/src/lib/jobs/index.ts b/apps/roomote/src/lib/jobs/index.ts new file mode 100644 index 0000000000..6ae4ffe6cc --- /dev/null +++ b/apps/roomote/src/lib/jobs/index.ts @@ -0,0 +1,3 @@ +export { fixGitHubIssue } from './fixGitHubIssue'; +export { processIssueComment } from './processIssueComment'; +export { processPullRequestComment } from './processPullRequestComment'; diff --git a/apps/roomote/src/lib/jobs/processIssueComment.ts b/apps/roomote/src/lib/jobs/processIssueComment.ts index 309706fd84..7c51ffe588 100644 --- a/apps/roomote/src/lib/jobs/processIssueComment.ts +++ b/apps/roomote/src/lib/jobs/processIssueComment.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as os from 'node:os'; -import type { JobType, JobPayload } from '@/types'; +import type { JobType, JobPayload } from '@roo-code-cloud/db'; import { runTask, type RunTaskCallbacks } from '../runTask'; import { Logger } from '../logger'; @@ -58,7 +58,6 @@ gh api repos/${jobPayload.repo}/issues/${jobPayload.issueNumber}/comments --meth jobType, jobPayload, prompt, - publish: async () => {}, logger: new Logger({ logDir: path.resolve(os.tmpdir(), 'logs'), filename: 'worker.log', diff --git a/apps/roomote/src/lib/jobs/processPullRequestComment.ts b/apps/roomote/src/lib/jobs/processPullRequestComment.ts index e2cadd88cb..097a2716ed 100644 --- a/apps/roomote/src/lib/jobs/processPullRequestComment.ts +++ b/apps/roomote/src/lib/jobs/processPullRequestComment.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as os from 'node:os'; -import type { JobType, JobPayload } from '@/types'; +import type { JobType, JobPayload } from '@roo-code-cloud/db'; import { runTask, type RunTaskCallbacks } from '../runTask'; import { Logger } from '../logger'; @@ -72,7 +72,6 @@ Do not create a new pull request - work directly on the existing PR branch. jobType, jobPayload, prompt, - publish: async () => {}, logger: new Logger({ logDir: path.resolve(os.tmpdir(), 'logs'), filename: 'worker.log', diff --git a/apps/roomote/src/lib/queue.ts b/apps/roomote/src/lib/queue.ts index a36fd79c10..f2824811e9 100644 --- a/apps/roomote/src/lib/queue.ts +++ b/apps/roomote/src/lib/queue.ts @@ -1,6 +1,6 @@ import { Queue, Job } from 'bullmq'; -import type { JobTypes, JobPayload, JobParams } from '@/types'; +import type { JobTypes, JobPayload, JobParams } from '@roo-code-cloud/db'; import { redis } from './redis'; diff --git a/apps/roomote/src/lib/runTask.ts b/apps/roomote/src/lib/runTask.ts index 92a14f7b61..dd62ed2a72 100644 --- a/apps/roomote/src/lib/runTask.ts +++ b/apps/roomote/src/lib/runTask.ts @@ -6,7 +6,7 @@ import pWaitFor from 'p-wait-for'; import { execa } from 'execa'; import { - type TaskEvent, + type RooCodeSettings, TaskCommandName, RooCodeEventName, IpcMessageType, @@ -14,7 +14,7 @@ import { } from '@roo-code/types'; import { IpcClient } from '@roo-code-cloud/ipc'; -import type { JobPayload, JobType } from '@/types'; +import type { JobPayload, JobType } from '@roo-code-cloud/db'; import { Logger } from './logger'; import { isDockerContainer } from './utils'; @@ -31,12 +31,12 @@ class SubprocessTimeoutError extends Error { export type RunTaskCallbacks = { onTaskStarted?: ( - slackThreadTs: string | null, + slackThreadTs: string | null | undefined, rooTaskId: string, ) => Promise; - onTaskAborted?: (slackThreadTs: string | null) => Promise; + onTaskAborted?: (slackThreadTs: string | null | undefined) => Promise; onTaskCompleted?: ( - slackThreadTs: string | null, + slackThreadTs: string | null | undefined, success: boolean, duration: number, rooTaskId?: string, @@ -49,24 +49,28 @@ type RunTaskOptions = { jobType: T; jobPayload: JobPayload; prompt: string; - publish: (taskEvent: TaskEvent) => Promise; logger: Logger; callbacks?: RunTaskCallbacks; + notify?: boolean; + workspacePath?: string; + settings?: RooCodeSettings; }; export const runTask = async ({ jobType, jobPayload, prompt, - publish, logger, callbacks, + notify = true, + workspacePath = '/roo/repos/Roo-Code', + settings = {}, }: RunTaskOptions) => { - const workspacePath = '/roo/repos/Roo-Code'; // findGitRoot(process.cwd()) const ipcSocketPath = path.resolve( os.tmpdir(), `${crypto.randomUUID().slice(0, 8)}.sock`, ); + const env = { ROO_CODE_IPC_SOCKET_PATH: ipcSocketPath }; const controller = new AbortController(); const cancelSignal = controller.signal; @@ -124,8 +128,8 @@ export const runTask = async ({ let rooTaskId: string | undefined; let isClientDisconnected = false; - const slackNotifier = new SlackNotifier(logger); - let slackThreadTs: string | null = null; + const slackNotifier = notify ? new SlackNotifier(logger) : undefined; + let slackThreadTs: string | null | undefined = null; const ignoreEvents: Record<'broadcast' | 'log', RooCodeEventName[]> = { broadcast: [RooCodeEventName.Message], @@ -138,11 +142,6 @@ export const runTask = async ({ client.on(IpcMessageType.TaskEvent, async (taskEvent) => { const { eventName, payload } = taskEvent; - // Publish all events except for these to Redis. - if (!ignoreEvents.broadcast.includes(eventName)) { - await publish({ ...taskEvent }); - } - // Log all events except for these. // For message events we only log non-partial messages. if ( @@ -158,7 +157,7 @@ export const runTask = async ({ rooTaskId = payload[0]; if (rooTaskId) { - slackThreadTs = await slackNotifier.postTaskStarted({ + slackThreadTs = await slackNotifier?.postTaskStarted({ jobType, jobPayload, rooTaskId, @@ -174,7 +173,7 @@ export const runTask = async ({ taskAbortedAt = Date.now(); if (slackThreadTs) { - await slackNotifier.postTaskUpdated( + await slackNotifier?.postTaskUpdated( slackThreadTs, 'Task was aborted', 'warning', @@ -190,7 +189,7 @@ export const runTask = async ({ taskFinishedAt = Date.now(); if (slackThreadTs) { - await slackNotifier.postTaskCompleted( + await slackNotifier?.postTaskCompleted( slackThreadTs, true, taskFinishedAt - taskStartedAt, @@ -220,6 +219,7 @@ export const runTask = async ({ configuration: { ...EVALS_SETTINGS, openRouterApiKey: process.env.OPENROUTER_API_KEY, + ...settings, }, text: prompt, newTab: true, @@ -239,7 +239,7 @@ export const runTask = async ({ logger.error('time limit reached'); if (slackThreadTs) { - await slackNotifier.postTaskUpdated( + await slackNotifier?.postTaskUpdated( slackThreadTs, 'Task timed out after 30 minutes', 'error', @@ -266,7 +266,7 @@ export const runTask = async ({ logger.error('client disconnected before task finished'); if (slackThreadTs) { - await slackNotifier.postTaskUpdated( + await slackNotifier?.postTaskUpdated( slackThreadTs, 'Client disconnected before task completion', 'error', diff --git a/apps/roomote/src/lib/slack.ts b/apps/roomote/src/lib/slack.ts index 62c4de191b..f3f9a7ca90 100644 --- a/apps/roomote/src/lib/slack.ts +++ b/apps/roomote/src/lib/slack.ts @@ -1,4 +1,5 @@ -import { JobPayload, JobType } from '@/types'; +import type { JobPayload, JobType } from '@roo-code-cloud/db'; + import { Logger } from './logger'; export interface SlackMessage { diff --git a/apps/roomote/src/lib/worker.ts b/apps/roomote/src/lib/worker.ts index 2cd2019a4b..c41c8355b4 100644 --- a/apps/roomote/src/lib/worker.ts +++ b/apps/roomote/src/lib/worker.ts @@ -10,16 +10,12 @@ import { processJob } from './job'; // --interactive \ // --tty \ // --network roo-code-cloud_default \ -// -e HOST_EXECUTION_METHOD=docker \ // -e GH_TOKEN=$GH_TOKEN \ -// -e DATABASE_URL=postgres://postgres:password@postgres:5432/development \ -// -e REDIS_URL=redis://redis:6379 \ -// -e NODE_ENV=production \ // -v /var/run/docker.sock:/var/run/docker.sock \ // -v /tmp/roomote:/var/log/roomote \ // roomote-worker sh -c "bash" -async function processSingleJob() { +async function processNextJob() { const worker = new Worker('roomote', undefined, { autorun: false, connection: redis, @@ -69,4 +65,4 @@ if (!process.env.GH_TOKEN) { throw new Error('GH_TOKEN is not set'); } -processSingleJob(); +processNextJob(); diff --git a/apps/roomote/src/types/index.ts b/apps/roomote/src/types/index.ts deleted file mode 100644 index 14ff057be2..0000000000 --- a/apps/roomote/src/types/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from 'zod'; - -export const createJobSchema = z.discriminatedUnion('type', [ - z.object({ - type: z.literal('github.issue.fix'), - payload: z.object({ - repo: z.string(), - issue: z.number(), - title: z.string(), - body: z.string(), - labels: z.array(z.string()).optional(), - }), - }), - z.object({ - type: z.literal('github.issue.comment.respond'), - payload: z.object({ - repo: z.string(), - issueNumber: z.number(), - issueTitle: z.string(), - issueBody: z.string(), - commentId: z.number(), - commentBody: z.string(), - commentAuthor: z.string(), - commentUrl: z.string(), - }), - }), - z.object({ - type: z.literal('github.pr.comment.respond'), - payload: z.object({ - repo: z.string(), - prNumber: z.number(), - prTitle: z.string(), - prBody: z.string(), - prBranch: z.string(), - baseRef: z.string(), - commentId: z.number(), - commentBody: z.string(), - commentAuthor: z.string(), - commentType: z.enum(['issue_comment', 'review_comment']), - commentUrl: z.string(), - }), - }), -]); - -export type CreateJob = z.infer; - -export type JobTypes = { - [K in CreateJob['type']]: Extract['payload']; -}; - -export type JobType = keyof JobTypes; - -export type JobStatus = 'pending' | 'processing' | 'completed' | 'failed'; - -export type JobPayload = JobTypes[T]; - -export type JobParams = { - jobId: number; - type: T; - payload: JobPayload; -}; diff --git a/apps/web/.env b/apps/web/.env index d6bcd2e54d..09592a12e7 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1,24 +1,9 @@ -# NOTE: Sensitive ENV vars are overridden with their real values in .env.local -# for local development and in the "Environment Variables" setting on Vercel -# for production (https://vercel.com/roo-code/roo-code-cloud/settings/environment-variables). -# Postgres -DATABASE_URL=postgres://foo:bar@baz:5432/jabberworky - -# ClickHouse -CLICKHOUSE_URL=https://fake.url -CLICKHOUSE_PASSWORD=fake-password - -# Clerk -CLERK_SECRET_KEY=fake-secret-key - -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... -NEXT_PUBLIC_CLERK_FRONTEND_API=https://fake.url - -# https://clerk.com/docs/deployments/clerk-environment-variables#sign-in-and-sign-up-redirects +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXBpYy1jaGFtb2lzLTg1LmNsZXJrLmFjY291bnRzLmRldiQ +NEXT_PUBLIC_CLERK_FRONTEND_API=https://epic-chamois-85.clerk.accounts.dev NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in +NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL=/ +NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/ NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up -NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL=/authorized -NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/authorized -NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/authorized -NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/authorized +NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/ +NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/ diff --git a/apps/web/.env.development b/apps/web/.env.development deleted file mode 100644 index 6e0821d034..0000000000 --- a/apps/web/.env.development +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://postgres:password@localhost:5432/development -CLICKHOUSE_URL=http://localhost:8123/default -CLICKHOUSE_PASSWORD=password diff --git a/apps/web/.env.production b/apps/web/.env.production new file mode 100644 index 0000000000..247b037a12 --- /dev/null +++ b/apps/web/.env.production @@ -0,0 +1,2 @@ +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_Y2xlcmsucm9vY29kZS5jb20k +NEXT_PUBLIC_CLERK_FRONTEND_API=https://clerk.roocode.com diff --git a/apps/web/.env.test b/apps/web/.env.test deleted file mode 100644 index 77c12cfbf7..0000000000 --- a/apps/web/.env.test +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://postgres:password@localhost:5432/test -CLICKHOUSE_URL=http://localhost:8123/default -CLICKHOUSE_PASSWORD=password diff --git a/apps/web/README.md b/apps/web/README.md index 31b6ce6847..a360b72ab1 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -49,10 +49,8 @@ Create an `.env.local` file and fill out the required secrets, including: - `CLICKHOUSE_URL` - `CLICKHOUSE_PASSWORD` - `CLERK_SECRET_KEY` -- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` -- `NEXT_PUBLIC_CLERK_FRONTEND_API` -You can now start the Next.js app and [Spotlight](https://spotlightjs.com/) with: +You can now start the Next.js app with: ```sh pnpm dev diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 797a5b7a68..11fe02eb9a 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,14 +1,9 @@ import type { NextConfig } from 'next'; -import withBundleAnalyzer from '@next/bundle-analyzer'; import { withSentryConfig } from '@sentry/nextjs'; import createNextIntlPlugin from 'next-intl/plugin'; const withNextIntl = createNextIntlPlugin(); -const bundleAnalyzer = withBundleAnalyzer({ - enabled: process.env.ANALYZE === 'true', -}); - const nextConfig: NextConfig = { eslint: { dirs: ['.'] }, poweredByHeader: false, @@ -20,7 +15,7 @@ const nextConfig: NextConfig = { // }, }; -export default withSentryConfig(bundleAnalyzer(withNextIntl(nextConfig)), { +export default withSentryConfig(withNextIntl(nextConfig), { // For all available options, see: // https://github.com/getsentry/sentry-webpack-plugin#options // FIXME: Add your Sentry organization and project names diff --git a/apps/web/package.json b/apps/web/package.json index bc80081a87..24e46c5d42 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,10 +4,10 @@ "scripts": { "lint": "next lint --max-warnings 0", "check-types": "tsc --noEmit", - "test": "dotenvx run -f .env.test -f .env -- vitest run", - "dev": "next dev --turbopack", - "build": "next build", - "start": "next start", + "test": "dotenvx run -f ../../.env.test -- vitest", + "dev": "dotenvx run -f ../../.env.development -- next dev --turbopack", + "build": "dotenvx run -f ../../.env.production -- next build", + "start": "dotenvx run -f ../../.env.production -- next start", "clean": "rimraf .next .turbo out" }, "dependencies": { @@ -16,7 +16,6 @@ "@clerk/themes": "^2.2.48", "@clickhouse/client": "^1.11.1", "@hookform/resolvers": "^5.0.1", - "@logtail/pino": "^0.5.5", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", @@ -32,9 +31,9 @@ "@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-tooltip": "^1.2.7", "@roo-code-cloud/db": "workspace:^", + "@roo-code-cloud/env": "workspace:^", "@roo-code/types": "^1.29.0", "@sentry/nextjs": "^9.23.0", - "@t3-oss/env-nextjs": "^0.13.6", "@tailwindcss/postcss": "^4.1.8", "@tanstack/react-query": "^5.79.0", "@tanstack/react-table": "^8.21.3", @@ -74,7 +73,6 @@ "devDependencies": { "@clerk/testing": "^1.7.5", "@eslint/js": "^9.27.0", - "@next/bundle-analyzer": "^15.3.3", "@next/eslint-plugin-next": "^15.3.3", "@roo-code-cloud/config-eslint": "workspace:^", "@roo-code-cloud/config-typescript": "workspace:^", diff --git a/apps/web/src/actions/auth.ts b/apps/web/src/actions/auth.ts index 865fb47787..5f193d740f 100644 --- a/apps/web/src/actions/auth.ts +++ b/apps/web/src/actions/auth.ts @@ -3,8 +3,10 @@ import { NextRequest } from 'next/server'; import { auth } from '@clerk/nextjs/server'; +import { Env } from '@roo-code-cloud/env'; + import { type AuthResult, type ApiAuthResult, isOrgRole } from '@/types'; -import { Env, logger } from '@/lib/server'; +import { logger } from '@/lib/server'; // import { // type AgentTokenPayload, // validateAgentToken, diff --git a/apps/web/src/instrumentation.ts b/apps/web/src/instrumentation.ts index 7bd117d4fa..cff186f30f 100644 --- a/apps/web/src/instrumentation.ts +++ b/apps/web/src/instrumentation.ts @@ -9,9 +9,6 @@ export async function register() { // Sentry DSN dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - // Enable Spotlight in development - spotlight: process.env.NODE_ENV === 'development', - // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, @@ -30,9 +27,6 @@ export async function register() { // Sentry DSN dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - // Enable Spotlight in development - spotlight: process.env.NODE_ENV === 'development', - // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, diff --git a/apps/web/src/lib/server/__tests__/agent-auth.test.ts b/apps/web/src/lib/server/__tests__/agent-auth.test.ts index a9b778aff4..c4587b523b 100644 --- a/apps/web/src/lib/server/__tests__/agent-auth.test.ts +++ b/apps/web/src/lib/server/__tests__/agent-auth.test.ts @@ -2,6 +2,12 @@ import jwt from 'jsonwebtoken'; +vi.mock('@roo-code-cloud/env', () => ({ + Env: { + CLERK_SECRET_KEY: 'test-secret-key-for-jwt-testing', + }, +})); + import { getAgentToken, refreshAgentTokenIfNeeded, @@ -10,14 +16,9 @@ import { } from '../agent-auth'; describe('agent-auth', () => { - const originalEnv = process.env; const testSecret = 'test-secret-key-for-jwt-testing'; beforeEach(() => { - // Reset environment variables - process.env = { ...originalEnv }; - process.env.CLERK_SECRET_KEY = testSecret; - // Mock Date.now() to return a consistent timestamp vi.useFakeTimers(); vi.setSystemTime(new Date('2023-01-01T00:00:00Z')); @@ -25,7 +26,6 @@ describe('agent-auth', () => { afterEach(() => { vi.useRealTimers(); - process.env = originalEnv; }); describe('getAgentToken', () => { @@ -51,7 +51,13 @@ describe('agent-auth', () => { }); it('should use fallback secret when CLERK_SECRET_KEY is not set', async () => { - delete process.env.CLERK_SECRET_KEY; + // Import the mocked module to modify it + const { Env } = await import('@roo-code-cloud/env'); + const mockedEnv = vi.mocked(Env); + + // Update the mock to use fallback secret + mockedEnv.CLERK_SECRET_KEY = 'fallback-secret'; + const token = await getAgentToken('agent-123', 'org-456'); // Token should still be valid with fallback secret @@ -61,6 +67,9 @@ describe('agent-auth', () => { // Verify it was signed with the fallback secret const decoded = jwt.verify(token, 'fallback-secret') as AgentTokenPayload; expect(decoded.sub).toBe('agent-123'); + + // Reset back to test secret + mockedEnv.CLERK_SECRET_KEY = testSecret; }); it('should set correct expiration time (15 minutes)', async () => { @@ -240,7 +249,13 @@ describe('agent-auth', () => { }); it('should use fallback secret when CLERK_SECRET_KEY is not set', async () => { - delete process.env.CLERK_SECRET_KEY; + // Import the mocked module to modify it + const { Env } = await import('@roo-code-cloud/env'); + const mockedEnv = vi.mocked(Env); + + // Update the mock to use fallback secret + mockedEnv.CLERK_SECRET_KEY = 'fallback-secret'; + const token = jwt.sign(validPayload, 'fallback-secret', { algorithm: 'HS256', }); @@ -248,6 +263,9 @@ describe('agent-auth', () => { const result = await validateAgentToken(token); expect(result).toEqual(validPayload); + + // Reset back to test secret + mockedEnv.CLERK_SECRET_KEY = testSecret; }); it('should throw error for invalid JWT signature', async () => { diff --git a/apps/web/src/lib/server/agent-auth.ts b/apps/web/src/lib/server/agent-auth.ts index 38c3ff1c06..3dac27200a 100644 --- a/apps/web/src/lib/server/agent-auth.ts +++ b/apps/web/src/lib/server/agent-auth.ts @@ -1,6 +1,8 @@ import jwt from 'jsonwebtoken'; import { z } from 'zod'; +import { Env } from '@roo-code-cloud/env'; + const agentTokenPayloadSchema = z.object({ sub: z.string().min(1, 'Subject (sub) is required'), org_id: z.string().min(1, 'Organization ID is required'), @@ -24,7 +26,7 @@ export async function getAgentToken(agentId: string, orgId: string) { iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 15 * 60, // 15 minutes }, - process.env.CLERK_SECRET_KEY || 'fallback-secret', + Env.CLERK_SECRET_KEY, { algorithm: 'HS256' }, ); } @@ -58,11 +60,9 @@ export async function validateAgentToken( token: string, ): Promise { try { - const rawPayload = jwt.verify( - token, - process.env.CLERK_SECRET_KEY || 'fallback-secret', - { algorithms: ['HS256'] }, - ); + const rawPayload = jwt.verify(token, Env.CLERK_SECRET_KEY, { + algorithms: ['HS256'], + }); const parseResult = agentTokenPayloadSchema.safeParse(rawPayload); diff --git a/apps/web/src/lib/server/analytics.ts b/apps/web/src/lib/server/analytics.ts index e9bee4e96d..4b38214a9b 100644 --- a/apps/web/src/lib/server/analytics.ts +++ b/apps/web/src/lib/server/analytics.ts @@ -1,6 +1,6 @@ import { createClient } from '@clickhouse/client'; -import { Env } from './env'; +import { Env } from '@roo-code-cloud/env'; export const analytics = createClient({ url: Env.CLICKHOUSE_URL, diff --git a/apps/web/src/lib/server/env.ts b/apps/web/src/lib/server/env.ts deleted file mode 100644 index 9c73c77b06..0000000000 --- a/apps/web/src/lib/server/env.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createEnv } from '@t3-oss/env-nextjs'; -import { z } from 'zod'; - -export const Env = createEnv({ - server: { - CLERK_SECRET_KEY: z.string().min(1), - DATABASE_URL: z.string(), - LOGTAIL_SOURCE_TOKEN: z.string().optional(), - CLICKHOUSE_URL: z.string().min(1), - CLICKHOUSE_PASSWORD: z.string().min(1), - }, - client: { - NEXT_PUBLIC_APP_URL: z.string().optional(), - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), - NEXT_PUBLIC_CLERK_SIGN_IN_URL: z.string().min(1), - NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL: z.string().min(1), - NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL: z.string().min(1), - NEXT_PUBLIC_CLERK_SIGN_UP_URL: z.string().min(1), - NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL: z.string().min(1), - NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL: z.string().min(1), - }, - shared: { - NODE_ENV: z.enum(['test', 'development', 'production']).optional(), - }, - // You need to destructure all the keys manually. - runtimeEnv: { - CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY, - DATABASE_URL: process.env.DATABASE_URL, - LOGTAIL_SOURCE_TOKEN: process.env.LOGTAIL_SOURCE_TOKEN, - NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL, - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, - NEXT_PUBLIC_CLERK_SIGN_IN_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL, - NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL: - process.env.NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL, - NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL: - process.env.NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL, - NEXT_PUBLIC_CLERK_SIGN_UP_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL, - NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL: - process.env.NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL, - NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL: - process.env.NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL, - NODE_ENV: process.env.NODE_ENV, - CLICKHOUSE_URL: process.env.CLICKHOUSE_URL, - CLICKHOUSE_PASSWORD: process.env.CLICKHOUSE_PASSWORD, - }, -}); diff --git a/apps/web/src/lib/server/index.ts b/apps/web/src/lib/server/index.ts index 074b4407b6..3a7b3352a0 100644 --- a/apps/web/src/lib/server/index.ts +++ b/apps/web/src/lib/server/index.ts @@ -1,4 +1,3 @@ -export { Env } from './env'; export { logger } from './logger'; export { handleError } from './api'; export { analytics } from './analytics'; diff --git a/apps/web/src/lib/server/logger.ts b/apps/web/src/lib/server/logger.ts index 3498f7d9a2..48e7c6dd20 100644 --- a/apps/web/src/lib/server/logger.ts +++ b/apps/web/src/lib/server/logger.ts @@ -1,17 +1,6 @@ import pino, { type DestinationStream } from 'pino'; import pretty from 'pino-pretty'; -import logtail from '@logtail/pino'; -import { Env } from './env'; - -const stream: DestinationStream = Env.LOGTAIL_SOURCE_TOKEN - ? pino.multistream([ - await logtail({ - sourceToken: Env.LOGTAIL_SOURCE_TOKEN, - options: { sendLogsToBetterStack: true }, - }), - { stream: pretty() }, // Prints logs to the console. - ]) - : pretty({ colorize: true }); +const stream: DestinationStream = pretty({ colorize: true }); export const logger = pino({ base: undefined }, stream); diff --git a/docker-compose.yml b/docker-compose.yml index 866058939c..caa5847b3c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,6 @@ name: roo-code-cloud services: - base: - build: - context: . - dockerfile: .docker/Dockerfile.base - image: roo-code-cloud-base - postgres: container_name: roo-code-cloud-postgres image: postgres:17.5 @@ -67,7 +61,7 @@ services: dockerfile: .docker/Dockerfile.roomote-dashboard image: roo-code-cloud-roomote-dashboard env_file: - - .env + - .env.keys ports: - "3002:3002" depends_on: @@ -81,7 +75,7 @@ services: dockerfile: .docker/Dockerfile.roomote-api image: roo-code-cloud-roomote-api env_file: - - .env + - .env.keys ports: - "3001:3001" volumes: @@ -97,11 +91,9 @@ services: build: context: . dockerfile: .docker/Dockerfile.roomote-controller - args: - - GH_TOKEN=${GH_TOKEN} image: roo-code-cloud-roomote-controller env_file: - - .env + - .env.keys volumes: - /var/run/docker.sock:/var/run/docker.sock - /tmp/roomote/controller:/var/log/roomote @@ -116,11 +108,9 @@ services: build: context: . dockerfile: .docker/Dockerfile.roomote-worker - args: - - GH_TOKEN=${GH_TOKEN} image: roomote-worker env_file: - - .env + - .env.keys volumes: - /var/run/docker.sock:/var/run/docker.sock - /tmp/roomote/worker:/var/log/roomote diff --git a/fly.roomote-api.toml b/fly.roomote-api.toml new file mode 100644 index 0000000000..99b8a3dd6c --- /dev/null +++ b/fly.roomote-api.toml @@ -0,0 +1,21 @@ +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# +# Deploy with: `fly deploy --config fly.roomote-api.toml` + +app = "roomote-api" +primary_region = "sjc" # See `fly platform regions` + +[env] + APP_ENV = "production" + +[build] + dockerfile = ".docker/Dockerfile.roomote-api" + ignorefile = ".dockerignore" + +[http_service] + internal_port = 3001 + force_https = true + min_machines_running = 0 + +[[vm]] + size = "performance-1x" # See `fly platform vm-sizes` diff --git a/fly.roomote-controller.toml b/fly.roomote-controller.toml new file mode 100644 index 0000000000..6abc77a094 --- /dev/null +++ b/fly.roomote-controller.toml @@ -0,0 +1,16 @@ +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# +# Deploy with: `fly deploy --config fly.roomote-controller.toml` + +app = "roomote-controller" +primary_region = "sjc" # See `fly platform regions` + +[env] + APP_ENV = "production" + +[build] + dockerfile = ".docker/Dockerfile.roomote-controller" + ignorefile = ".dockerignore" + +[[vm]] + size = "performance-2x" # See `fly platform vm-sizes` diff --git a/fly.roomote-dashboard.toml b/fly.roomote-dashboard.toml new file mode 100644 index 0000000000..628b9bbd0f --- /dev/null +++ b/fly.roomote-dashboard.toml @@ -0,0 +1,21 @@ +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# +# Deploy with: `fly deploy --config fly.roomote-dashboard.toml` + +app = "roomote-dashboard" +primary_region = "sjc" # See `fly platform regions` + +[env] + APP_ENV = "production" + +[build] + dockerfile = ".docker/Dockerfile.roomote-dashboard" + ignorefile = ".dockerignore" + +[http_service] + internal_port = 3002 + force_https = true + min_machines_running = 0 + +[[vm]] + size = "performance-1x" # See `fly platform vm-sizes` diff --git a/package.json b/package.json index c7caa2191a..220bf7da53 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,22 @@ "test": "turbo test --log-order grouped --output-logs new-only", "build": "turbo build --log-order grouped --output-logs new-only", "clean": "turbo clean --log-order grouped --output-logs new-only && rimraf .turbo", - "db:up": "docker compose up postgres clickhouse -d --wait && pnpm --filter @roo-code-cloud/db db:migrate && pnpm --filter @roo-code-cloud/db db:test:push --force", - "db:down": "docker compose down postgres clickhouse", + "db:up": "docker compose up postgres clickhouse redis -d --wait && pnpm --filter @roo-code-cloud/db db:migrate && pnpm --filter @roo-code-cloud/db db:test:push --force", + "db:down": "docker compose down postgres clickhouse redis", "db:generate": "pnpm --filter @roo-code-cloud/db db:generate", "db:migrate": "pnpm --filter @roo-code-cloud/db db:migrate", "db:reset": "pnpm db:down && rimraf .docker/data .docker/logs && pnpm db:up", - "services:build": "docker compose build base && docker compose build roomote-api roomote-dashboard roomote-controller roomote-worker", - "services:up": "docker compose up redis roomote-dashboard roomote-api -d --wait", - "services:down": "docker compose down redis roomote-dashboard roomote-api", - "tunnel": "ngrok http 3001 --domain cte.ngrok.dev" + "services:build": "docker compose build --build-arg GH_TOKEN=$(npx dotenvx get GH_TOKEN -f .env.development) roomote-dashboard roomote-api roomote-controller roomote-worker", + "services:up": "docker compose up roomote-dashboard roomote-api -d --wait", + "services:down": "docker compose down roomote-dashboard roomote-api", + "tunnel": "ngrok http 3001 --domain cte.ngrok.dev", + "env:decrypt:test": "dotenvx decrypt -f .env.test --stdout", + "env:decrypt:development": "dotenvx decrypt -f .env.development --stdout", + "env:decrypt:preview": "dotenvx decrypt -f .env.preview --stdout", + "env:decrypt:production": "dotenvx decrypt -f .env.production --stdout", + "deploy:roomote-dashboard": "fly deploy --config fly.roomote-dashboard.toml", + "deploy:roomote-api": "fly deploy --config fly.roomote-api.toml", + "deploy:roomote-controller": "fly deploy --config fly.roomote-controller.toml" }, "devDependencies": { "@dotenvx/dotenvx": "^1.44.2", diff --git a/packages/db/.env.development b/packages/db/.env.development deleted file mode 100644 index 6e0821d034..0000000000 --- a/packages/db/.env.development +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://postgres:password@localhost:5432/development -CLICKHOUSE_URL=http://localhost:8123/default -CLICKHOUSE_PASSWORD=password diff --git a/packages/db/.env.test b/packages/db/.env.test deleted file mode 100644 index 77c12cfbf7..0000000000 --- a/packages/db/.env.test +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://postgres:password@localhost:5432/test -CLICKHOUSE_URL=http://localhost:8123/default -CLICKHOUSE_PASSWORD=password diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts index 4f185d7fda..638ee1637e 100644 --- a/packages/db/drizzle.config.ts +++ b/packages/db/drizzle.config.ts @@ -1,10 +1,12 @@ import { defineConfig } from 'drizzle-kit'; +import { Env } from '@roo-code-cloud/env'; + export default defineConfig({ schema: './src/schema.ts', out: './drizzle', dialect: 'postgresql', dbCredentials: { - url: process.env.DATABASE_URL!, + url: Env.DATABASE_URL, }, }); diff --git a/packages/db/package.json b/packages/db/package.json index 1180d189b8..00b37ab96f 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -19,13 +19,17 @@ "lint": "eslint src --ext=ts --max-warnings=0", "check-types": "tsc --noEmit", "clean": "rimraf .turbo", - "drizzle-kit": "tsx node_modules/drizzle-kit/bin.cjs", - "db:generate": "dotenvx run -f .env.development -- drizzle-kit generate", - "db:migrate": "dotenvx run -f .env.development -- drizzle-kit migrate", - "db:push": "dotenvx run -f .env.development -- drizzle-kit push", - "db:test:push": "dotenvx run -f .env.test -- drizzle-kit push" + "drizzle-kit:test": "NODE_ENV=test dotenvx run -f ../../.env.test -- tsx node_modules/drizzle-kit/bin.cjs", + "drizzle-kit:development": "NODE_ENV=development dotenvx run -f ../../.env.development -- tsx node_modules/drizzle-kit/bin.cjs", + "drizzle-kit:production": "NODE_ENV=production dotenvx run -f ../../.env.production -- tsx node_modules/drizzle-kit/bin.cjs", + "db:generate": "pnpm drizzle-kit:development generate", + "db:migrate": "pnpm drizzle-kit:development migrate", + "db:push": "pnpm drizzle-kit:development push", + "db:test:push": "pnpm drizzle-kit:test push", + "db:production:migrate": "pnpm drizzle-kit:production migrate" }, "dependencies": { + "@roo-code-cloud/env": "workspace:^", "@roo-code/types": "^1.29.0", "drizzle-orm": "^0.44.2", "postgres": "^3.4.7", diff --git a/packages/db/src/db.ts b/packages/db/src/db.ts index ed43571276..fdec200290 100644 --- a/packages/db/src/db.ts +++ b/packages/db/src/db.ts @@ -1,18 +1,19 @@ import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; +import { Env } from '@roo-code-cloud/env'; + import * as schema from './schema'; -const client = postgres(process.env.DATABASE_URL!, { prepare: false }); +const client = postgres(Env.DATABASE_URL, { prepare: false }); const db = drizzle({ client, schema }); -if (process.env.NODE_ENV === 'test') { - if ( - !process.env.DATABASE_URL!.includes('test') || - !process.env.DATABASE_URL!.includes('localhost') - ) { - throw new Error('DATABASE_URL is not a test database'); - } +if ( + Env.NODE_ENV === 'test' && + (!Env.DATABASE_URL.includes('test') || + !Env.DATABASE_URL.includes('localhost')) +) { + throw new Error('DATABASE_URL is not a test database'); } const disconnect = async () => client.end(); diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index d504c35496..82206cd30a 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -122,6 +122,12 @@ export const createJobSchema = z.discriminatedUnion('type', [ commentUrl: z.string(), }), }), + z.object({ + type: z.literal('test.prompt'), + payload: z.object({ + text: z.string(), + }), + }), ]); export type CreateJob = z.infer; @@ -129,7 +135,6 @@ export type CreateJob = z.infer; export type JobTypes = { [K in CreateJob['type']]: Extract['payload']; }; - /** * JobType, JobStatus, JobPayload, JobParams */ diff --git a/packages/env/eslint.config.mjs b/packages/env/eslint.config.mjs new file mode 100644 index 0000000000..c072244298 --- /dev/null +++ b/packages/env/eslint.config.mjs @@ -0,0 +1,4 @@ +import { config } from "@roo-code-cloud/config-eslint/base" + +/** @type {import("eslint").Linter.Config} */ +export default [...config] diff --git a/packages/env/package.json b/packages/env/package.json new file mode 100644 index 0000000000..11b03844d7 --- /dev/null +++ b/packages/env/package.json @@ -0,0 +1,23 @@ +{ + "name": "@roo-code-cloud/env", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "lint": "eslint src --ext=ts --max-warnings=0", + "check-types": "tsc --noEmit", + "test": "dotenvx run -f ../../.env.test -- vitest", + "clean": "rimraf .turbo" + }, + "dependencies": { + "@roo-code/types": "^1.26.0", + "@t3-oss/env-nextjs": "^0.13.6", + "zod": "^3.25.41" + }, + "devDependencies": { + "@roo-code-cloud/config-eslint": "workspace:^", + "@roo-code-cloud/config-typescript": "workspace:^", + "@types/node": "^24.0.3", + "vitest": "^3.2.4" + } +} diff --git a/packages/env/src/__tests__/index.test.ts b/packages/env/src/__tests__/index.test.ts new file mode 100644 index 0000000000..d2b2d0e0be --- /dev/null +++ b/packages/env/src/__tests__/index.test.ts @@ -0,0 +1,9 @@ +import { Env } from '../index'; + +describe('Env', () => { + it('should be defined', () => { + expect(Env.NODE_ENV).toBeDefined(); + expect(Env.DATABASE_URL).toContain('postgres://'); + expect(Env.DATABASE_URL).toContain('test'); + }); +}); diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts new file mode 100644 index 0000000000..aed7d74a9d --- /dev/null +++ b/packages/env/src/index.ts @@ -0,0 +1,25 @@ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; + +export const Env = createEnv({ + shared: { + NODE_ENV: z.enum(['test', 'development', 'production']), + }, + server: { + DATABASE_URL: z.string(), + REDIS_URL: z.string(), + CLICKHOUSE_URL: z.string().min(1), + CLICKHOUSE_PASSWORD: z.string().min(1), + CLERK_SECRET_KEY: z.string().min(1), + }, + client: {}, + // You need to destructure all the keys manually. + runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + DATABASE_URL: process.env.DATABASE_URL, + REDIS_URL: process.env.REDIS_URL, + CLICKHOUSE_URL: process.env.CLICKHOUSE_URL, + CLICKHOUSE_PASSWORD: process.env.CLICKHOUSE_PASSWORD, + CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY, + }, +}); diff --git a/packages/env/tsconfig.json b/packages/env/tsconfig.json new file mode 100644 index 0000000000..754dfe18b7 --- /dev/null +++ b/packages/env/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@roo-code-cloud/config-typescript/base.json", + "compilerOptions": { + "types": ["vitest/globals"] + }, + "include": ["src", "scripts"], + "exclude": ["node_modules"] +} diff --git a/packages/env/vitest.config.ts b/packages/env/vitest.config.ts new file mode 100644 index 0000000000..da651da521 --- /dev/null +++ b/packages/env/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + watch: false, + reporters: ['dot'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38c9c075d0..6f46259962 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@roo-code-cloud/db': specifier: workspace:^ version: link:../../packages/db + '@roo-code-cloud/env': + specifier: workspace:^ + version: link:../../packages/env '@roo-code-cloud/ipc': specifier: workspace:^ version: link:../../packages/ipc @@ -51,6 +54,9 @@ importers: bullmq: specifier: ^5.37.0 version: 5.54.3 + cmd-ts: + specifier: ^0.13.0 + version: 0.13.0 drizzle-orm: specifier: ^0.44.2 version: 0.44.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.0)(@types/pg@8.15.2)(pg@8.15.6)(postgres@3.4.7) @@ -152,9 +158,6 @@ importers: '@hookform/resolvers': specifier: ^5.0.1 version: 5.0.1(react-hook-form@7.56.4(react@19.1.0)) - '@logtail/pino': - specifier: ^0.5.5 - version: 0.5.5(pino@9.7.0) '@radix-ui/react-accordion': specifier: ^1.2.11 version: 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -200,15 +203,15 @@ importers: '@roo-code-cloud/db': specifier: workspace:^ version: link:../../packages/db + '@roo-code-cloud/env': + specifier: workspace:^ + version: link:../../packages/env '@roo-code/types': specifier: ^1.29.0 version: 1.29.0 '@sentry/nextjs': specifier: ^9.23.0 version: 9.23.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9) - '@t3-oss/env-nextjs': - specifier: ^0.13.6 - version: 0.13.6(arktype@2.1.20)(typescript@5.8.3)(zod@3.25.41) '@tailwindcss/postcss': specifier: ^4.1.8 version: 4.1.8 @@ -321,9 +324,6 @@ importers: '@eslint/js': specifier: ^9.27.0 version: 9.27.0 - '@next/bundle-analyzer': - specifier: ^15.3.3 - version: 15.3.3 '@next/eslint-plugin-next': specifier: ^15.3.3 version: 15.3.3 @@ -434,6 +434,9 @@ importers: packages/db: dependencies: + '@roo-code-cloud/env': + specifier: workspace:^ + version: link:../env '@roo-code/types': specifier: ^1.29.0 version: 1.29.0 @@ -460,6 +463,31 @@ importers: specifier: ^0.31.1 version: 0.31.1 + packages/env: + dependencies: + '@roo-code/types': + specifier: ^1.26.0 + version: 1.29.0 + '@t3-oss/env-nextjs': + specifier: ^0.13.6 + version: 0.13.6(arktype@2.1.20)(typescript@5.8.3)(zod@3.25.41) + zod: + specifier: ^3.25.41 + version: 3.25.41 + devDependencies: + '@roo-code-cloud/config-eslint': + specifier: workspace:^ + version: link:../config-eslint + '@roo-code-cloud/config-typescript': + specifier: workspace:^ + version: link:../config-typescript + '@types/node': + specifier: ^24.0.3 + version: 24.0.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + packages/ipc: dependencies: '@roo-code/types': @@ -721,10 +749,6 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@discoveryjs/json-ext@0.5.7': - resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} - engines: {node: '>=10.0.0'} - '@dotenvx/dotenvx@1.44.2': resolution: {integrity: sha512-2C44+G2dch4cB6zw7+oGQ9VcFQuuVhc5xOzfVvY7iUEj2PRhiVMIB6SpNMK1V5TvpdqrAqCYFjclK18Mh9vwNQ==} hasBin: true @@ -1290,27 +1314,6 @@ packages: '@libsql/core@0.15.9': resolution: {integrity: sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw==} - '@logtail/core@0.5.4': - resolution: {integrity: sha512-UlbxvUg5MDroqk6gEhtfKuuBShrvD/VaXl3T3jGU1U6I19Y1OwfB7e681LvH7RbsCAnOJFziM/7rMQib/CJTdg==} - - '@logtail/node@0.5.5': - resolution: {integrity: sha512-hSZDjl2wFRxe/+pxSlYNSpm0V3a2ZsiWqBifvtCBHTriwrNkd0LjQtedRlrjoBBumVxIMGdDOCPayAIbcux5YQ==} - - '@logtail/pino@0.5.5': - resolution: {integrity: sha512-RNGWSWmqXHmchWm0MZS69JfnEAULck2uTrbh0/rZDBhnsZjsUTXmviDsA+zfjLr6Y702CmPLwx7ctlhHK7pbEw==} - peerDependencies: - pino: ^7.0.0 || ^8.0.0 || ^9.0.0 - - '@logtail/tools@0.5.4': - resolution: {integrity: sha512-S0PtfWOVVgvb36q8mLksMw910mO2TahDaqIYnUM/ZEYX+AwqKRGov4zt/X33zHRzCFVpSKoZFv7bFs8TBie/pQ==} - - '@logtail/types@0.5.3': - resolution: {integrity: sha512-5uKf1xQ7wRqSW0lYIwDpXdXcFHxPKH5EM22MoEjwTpd8suymOYuEdx9qCCHCYey4uOhPq7x6OkZf86bTO83JsA==} - - '@msgpack/msgpack@2.8.0': - resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} - engines: {node: '>= 10'} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -1341,9 +1344,6 @@ packages: cpu: [x64] os: [win32] - '@next/bundle-analyzer@15.3.3': - resolution: {integrity: sha512-9gddnjACK6yOa5IkmeFyzcwZh2rscsb6ZspTd7tymPYKQM96fJuKjn9HrRtPNKiMm7ExKNadAJqREmHdBgHZ9A==} - '@next/env@15.3.3': resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} @@ -1615,9 +1615,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/instrumentation@6.8.2': resolution: {integrity: sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q==} peerDependencies: @@ -2294,9 +2291,6 @@ packages: '@roo-code/types@1.29.0': resolution: {integrity: sha512-bovFOCVQYuWnV2Cvdcx5tWp8aUpF0tEoxN0rS05hzzwdDeDxB6F6q5orKsQqghTS0yqv5pYclQLZRyAV7oBsbw==} - '@roo-code/types@1.28.0': - resolution: {integrity: sha512-kJAWDaY80BLPRxVa1Nz3Fuj7tvnSVMsRlpgNq3YXvFyQwWsui7DZz/AgX5qm+CwcAHXHBPmCUn5oV5XnEAEykw==} - '@schummar/icu-type-parser@1.21.5': resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==} @@ -2762,9 +2756,6 @@ packages: '@types/shimmer@1.2.0': resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} - '@types/stack-trace@0.0.33': - resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==} - '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} @@ -2923,10 +2914,6 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -2941,15 +2928,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -3084,9 +3062,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -3116,9 +3091,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bullmq@5.54.3: resolution: {integrity: sha512-MVK2pOkB3hvrIcubwI8dS4qWHJLNKakKPpgRBTw91sIpPZArmvZ4t2hvryyEaJXJbAS/JHd6pKYOUd+RGRkWQQ==} @@ -3236,6 +3208,9 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + cmd-ts@0.13.0: + resolution: {integrity: sha512-nsnxf6wNIM/JAS7T/x/1JmbEsjH0a8tezXqqpaL0O6+eV0/aDEnRxwjxpu0VzDdRcaC1ixGSbRlUuf/IU59I4g==} + cmdk@1.1.1: resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} peerDependencies: @@ -3273,10 +3248,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -3410,9 +3381,6 @@ packages: dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - debounce@1.2.1: - resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -3476,6 +3444,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -3604,9 +3575,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3822,10 +3790,6 @@ packages: resolution: {integrity: sha512-2QiHxshejKgJrYMzSI9MEHrvhmzxBL+eLyiM5IiyjDBySkgwS2+tdtnO3gbx8pEisu/yOFCIhfCb63gCEu0yBQ==} engines: {node: '>=13.0.0'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -4080,10 +4044,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - gzip-size@6.0.0: - resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} - engines: {node: '>=10'} - hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -4138,9 +4098,6 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -4180,9 +4137,6 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4344,10 +4298,6 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -4669,9 +4619,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} - loupe@3.1.4: resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} @@ -4916,10 +4863,6 @@ packages: module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5105,10 +5048,6 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - opener@1.5.2: - resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} - hasBin: true - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -5273,9 +5212,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - pino-abstract-transport@1.2.0: - resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} - pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} @@ -5388,10 +5324,6 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -5543,10 +5475,6 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -5728,10 +5656,6 @@ packages: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} - serialize-error@8.1.0: - resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} - engines: {node: '>=10'} - serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -5805,10 +5729,6 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - sirv@2.0.4: - resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} - engines: {node: '>= 10'} - slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -5870,9 +5790,6 @@ packages: stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} - stack-trace@0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -5952,9 +5869,6 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -6137,10 +6051,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - tough-cookie@5.1.2: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} @@ -6231,10 +6141,6 @@ packages: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -6497,11 +6403,6 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webpack-bundle-analyzer@4.10.1: - resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==} - engines: {node: '>= 10.13.0'} - hasBin: true - webpack-sources@3.3.0: resolution: {integrity: sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==} engines: {node: '>=10.13.0'} @@ -6588,18 +6489,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -6975,8 +6864,6 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@discoveryjs/json-ext@0.5.7': {} - '@dotenvx/dotenvx@1.44.2': dependencies: commander: 11.1.0 @@ -7394,36 +7281,6 @@ snapshots: js-base64: 3.7.7 optional: true - '@logtail/core@0.5.4': - dependencies: - '@logtail/tools': 0.5.4 - '@logtail/types': 0.5.3 - serialize-error: 8.1.0 - - '@logtail/node@0.5.5': - dependencies: - '@logtail/core': 0.5.4 - '@logtail/types': 0.5.3 - '@msgpack/msgpack': 2.8.0 - '@types/stack-trace': 0.0.33 - minimatch: 9.0.5 - stack-trace: 0.0.10 - - '@logtail/pino@0.5.5(pino@9.7.0)': - dependencies: - '@logtail/node': 0.5.5 - '@logtail/types': 0.5.3 - pino: 9.7.0 - pino-abstract-transport: 1.2.0 - - '@logtail/tools@0.5.4': - dependencies: - '@logtail/types': 0.5.3 - - '@logtail/types@0.5.3': {} - - '@msgpack/msgpack@2.8.0': {} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -7442,13 +7299,6 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@next/bundle-analyzer@15.3.3': - dependencies: - webpack-bundle-analyzer: 4.10.1 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - '@next/env@15.3.3': {} '@next/eslint-plugin-next@15.3.3': @@ -7746,8 +7596,6 @@ snapshots: playwright: 1.52.0 optional: true - '@polka/url@1.0.0-next.29': {} - '@prisma/instrumentation@6.8.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -8359,8 +8207,6 @@ snapshots: '@roo-code/types@1.29.0': {} - '@roo-code/types@1.28.0': {} - '@schummar/icu-type-parser@1.21.5': {} '@sec-ant/readable-stream@0.4.1': {} @@ -8850,7 +8696,7 @@ snapshots: '@types/pg@8.15.2': dependencies: '@types/node': 22.15.32 - pg-protocol: 1.10.0 + pg-protocol: 1.10.2 pg-types: 4.0.2 '@types/pg@8.6.1': @@ -8884,8 +8730,6 @@ snapshots: '@types/shimmer@1.2.0': {} - '@types/stack-trace@0.0.33': {} - '@types/tedious@4.0.14': dependencies: '@types/node': 22.15.32 @@ -9032,6 +8876,14 @@ snapshots: optionalDependencies: vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -9140,10 +8992,6 @@ snapshots: '@xtuc/long@4.2.2': {} - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -9157,12 +9005,6 @@ snapshots: dependencies: acorn: 8.15.0 - acorn-walk@8.3.4: - dependencies: - acorn: 8.15.0 - - acorn@8.14.1: {} - acorn@8.15.0: {} agent-base@6.0.2: @@ -9308,8 +9150,6 @@ snapshots: balanced-match@1.0.2: {} - base64-js@1.5.1: {} - binary-extensions@2.3.0: {} body-parser@2.2.0: @@ -9350,11 +9190,6 @@ snapshots: buffer-from@1.1.2: {} - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bullmq@5.54.3: dependencies: cron-parser: 4.9.0 @@ -9411,7 +9246,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 + loupe: 3.1.4 pathval: 2.0.0 chalk@3.0.0: @@ -9479,6 +9314,15 @@ snapshots: cluster-key-slot@1.1.2: {} + cmd-ts@0.13.0: + dependencies: + chalk: 4.1.2 + debug: 4.4.1 + didyoumean: 1.2.2 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.6)(react@19.1.0) @@ -9519,8 +9363,6 @@ snapshots: commander@2.20.3: {} - commander@7.2.0: {} - commondir@1.0.1: {} concat-map@0.0.1: {} @@ -9650,8 +9492,6 @@ snapshots: dateformat@4.6.3: {} - debounce@1.2.1: {} - debug@4.4.1: dependencies: ms: 2.1.3 @@ -9701,6 +9541,8 @@ snapshots: dependencies: dequal: 2.0.3 + didyoumean@1.2.2: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -9749,8 +9591,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexer@0.1.2: {} - eastasianwidth@0.2.0: {} easy-stack@1.0.1: {} @@ -10101,8 +9941,6 @@ snapshots: copyfiles: 2.4.1 strong-type: 0.1.6 - event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} @@ -10397,10 +10235,6 @@ snapshots: graphemer@1.4.0: {} - gzip-size@6.0.0: - dependencies: - duplexer: 0.1.2 - hard-rejection@2.1.0: {} has-bigints@1.1.0: {} @@ -10465,8 +10299,6 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 - html-escaper@2.0.2: {} - html-url-attributes@3.0.1: {} http-errors@2.0.0: @@ -10510,8 +10342,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} - ignore@5.3.2: {} ignore@7.0.4: {} @@ -10676,8 +10506,6 @@ snapshots: is-plain-obj@4.1.0: {} - is-plain-object@5.0.0: {} - is-potential-custom-element-name@1.0.1: {} is-promise@4.0.0: {} @@ -10994,8 +10822,6 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.1.3: {} - loupe@3.1.4: {} lower-case@2.0.2: @@ -11348,8 +11174,6 @@ snapshots: module-details-from-path@1.0.4: {} - mrmime@2.0.1: {} - ms@2.1.3: {} msgpackr-extract@3.0.3: @@ -11556,8 +11380,6 @@ snapshots: dependencies: mimic-function: 5.0.1 - opener@1.5.2: {} - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -11673,8 +11495,7 @@ snapshots: pg-protocol@1.10.0: {} - pg-protocol@1.10.2: - optional: true + pg-protocol@1.10.2: {} pg-types@2.2.0: dependencies: @@ -11718,11 +11539,6 @@ snapshots: pidtree@0.6.0: {} - pino-abstract-transport@1.2.0: - dependencies: - readable-stream: 4.7.0 - split2: 4.2.0 - pino-abstract-transport@2.0.0: dependencies: split2: 4.2.0 @@ -11834,8 +11650,6 @@ snapshots: process-warning@5.0.0: {} - process@0.11.10: {} - progress@2.0.3: {} prop-types@15.8.1: @@ -12018,14 +11832,6 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -12288,10 +12094,6 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-error@8.1.0: - dependencies: - type-fest: 0.20.2 - serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -12409,12 +12211,6 @@ snapshots: is-arrayish: 0.3.2 optional: true - sirv@2.0.4: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 @@ -12478,8 +12274,6 @@ snapshots: dependencies: stackframe: 1.3.4 - stack-trace@0.0.10: {} - stackback@0.0.2: {} stackframe@1.3.4: {} @@ -12584,10 +12378,6 @@ snapshots: dependencies: safe-buffer: 5.1.2 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -12737,8 +12527,6 @@ snapshots: toidentifier@1.0.1: {} - totalist@3.0.1: {} - tough-cookie@5.1.2: dependencies: tldts: 6.1.86 @@ -12807,8 +12595,6 @@ snapshots: type-fest@0.18.1: {} - type-fest@0.20.2: {} - type-fest@0.6.0: {} type-fest@0.7.1: {} @@ -13073,6 +12859,27 @@ snapshots: - tsx - yaml + vite-node@3.2.4(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.27)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): dependencies: debug: 4.4.1 @@ -13135,6 +12942,23 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 + vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.5(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.4 + rollup: 4.41.1 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.0.3 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + terser: 5.43.1 + tsx: 4.20.3 + yaml: 2.8.0 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -13264,6 +13088,49 @@ snapshots: - tsx - yaml + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 24.0.3 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -13279,25 +13146,6 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-bundle-analyzer@4.10.1: - dependencies: - '@discoveryjs/json-ext': 0.5.7 - acorn: 8.14.1 - acorn-walk: 8.3.4 - commander: 7.2.0 - debounce: 1.2.1 - escape-string-regexp: 4.0.0 - gzip-size: 6.0.0 - html-escaper: 2.0.2 - is-plain-object: 5.0.0 - opener: 1.5.2 - picocolors: 1.1.1 - sirv: 2.0.4 - ws: 7.5.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - webpack-sources@3.3.0: {} webpack-sources@3.3.3: {} @@ -13427,8 +13275,6 @@ snapshots: wrappy@1.0.2: {} - ws@7.5.10: {} - ws@8.18.2: {} xml-name-validator@5.0.0: {} diff --git a/scripts/find-missing-translations.ts b/scripts/find-missing-translations.ts deleted file mode 100644 index fb9e488ee7..0000000000 --- a/scripts/find-missing-translations.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -// npx tsx scripts/find-missing-translations.ts - -import fs from 'fs'; -import path from 'path'; - -function extractKeys(obj: any, prefix = ''): string[] { - let keys: string[] = []; - - for (const key in obj) { - const currentKey = prefix ? `${prefix}.${key}` : key; - - if (typeof obj[key] === 'object' && obj[key] !== null) { - keys = [...keys, ...extractKeys(obj[key], currentKey)]; - } else { - keys.push(currentKey); - } - } - - return keys; -} - -async function main() { - const rootDir = path.resolve(__dirname, '..'); - const localesDir = path.join('src', 'i18n', 'locales'); - - const files = fs - .readdirSync(path.join(rootDir, localesDir)) - .filter((file) => file.endsWith('.json')) - .map((file) => path.join(rootDir, localesDir, file)); - - const localeKeys: { [locale: string]: Set } = {}; - const allKeys = new Set(); - - for (const file of files) { - const keys = extractKeys(JSON.parse(fs.readFileSync(file, 'utf8'))); - const relativePath = path.join(localesDir, path.basename(file)); - localeKeys[relativePath] = new Set(keys); - - for (const key of keys) { - allKeys.add(key); - } - } - - Object.entries(localeKeys).forEach(([locale, keys]) => { - const missingKeys = Array.from(allKeys).filter((key) => !keys.has(key)); - - if (missingKeys.length > 0) { - console.log( - `\n[${locale}] Missing ${missingKeys.length} translation(s):`, - ); - - const groupedMissing = new Map(); - - for (const key of missingKeys) { - const parts = key.split('.'); - - if (parts.length >= 2) { - const namespace = parts[0]; - const subKey = parts.slice(1).join('.'); - - if (namespace && subKey) { - if (!groupedMissing.has(namespace)) { - groupedMissing.set(namespace, []); - } - - const group = groupedMissing.get(namespace); - - if (group) { - group.push(subKey); - } - } - } - } - - for (const [namespace, keys] of groupedMissing.entries()) { - console.log(`${namespace}`); - keys.forEach((key) => console.log(`\t${key}`)); - } - } else { - console.log(`\n[${locale}] No missing translations`); - } - }); -} - -main(); diff --git a/scripts/test-agent-endpoint.ts b/scripts/test-agent-endpoint.ts deleted file mode 100644 index f62b0dae66..0000000000 --- a/scripts/test-agent-endpoint.ts +++ /dev/null @@ -1,221 +0,0 @@ -// npx dotenvx run -f .env.development -f .env.local -- tsx scripts/test-agent-endpoint.ts [orgId] [agent] [baseUrl] - -import postgres from 'postgres'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { eq } from 'drizzle-orm'; -import crypto from 'crypto'; - -import { agents, users } from '@/db/schema'; -import { getAgentToken } from '@/lib/server/agent-auth'; - -const client = postgres(process.env.DATABASE_URL!); -const db = drizzle(client); - -interface TestConfig { - orgId: string; - agentName: string; - baseUrl: string; -} - -async function createTestAgent(config: TestConfig): Promise { - console.log('๐Ÿค– Creating test agent...'); - - try { - const existingAgent = await db - .select() - .from(agents) - .where(eq(agents.displayName, config.agentName)) - .limit(1); - - if (existingAgent.length > 0) { - console.log(`โœ… Using existing agent: ${existingAgent[0]!.id}`); - return existingAgent[0]!.id; - } - - const existingUsers = await db.select().from(users).limit(1); - let createdByUserId: string; - - if (existingUsers.length > 0) { - createdByUserId = existingUsers[0]!.id; - console.log(`๐Ÿ“‹ Using existing user as creator: ${createdByUserId}`); - } else { - const testUserId = `user_${crypto.randomBytes(12).toString('hex')}`; - - const testUserData = { - id: testUserId, - orgId: config.orgId, - orgRole: 'admin', - name: 'Test User', - email: 'test@example.com', - imageUrl: 'https://example.com/avatar.png', - entity: {}, - }; - - console.log(`๐Ÿ‘ค Creating test user: ${testUserId}`); - const [newUser] = await db.insert(users).values(testUserData).returning(); - createdByUserId = newUser!.id; - } - - const agentId = `agent_${crypto.randomBytes(12).toString('hex')}`; - - const agentData = { - id: agentId, - orgId: config.orgId, - displayName: config.agentName, - description: 'Test agent created by script', - createdByUserId: createdByUserId, - }; - - const [newAgent] = await db.insert(agents).values(agentData).returning(); - - if (!newAgent) { - throw new Error('Failed to create agent in database'); - } - - console.log(`โœ… Created agent: ${newAgent.id}`); - return newAgent.id; - } catch (error) { - console.error('โŒ Failed to create agent:', error); - throw error; - } -} - -async function testEndpoint(token: string, baseUrl: string) { - console.log('๐ŸŒ Testing endpoint...'); - - const url = `${baseUrl}/api/me`; - console.log(`๐Ÿ“ก Request URL: ${url}`); - - try { - const response = await fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - 'User-Agent': 'TestScript/1.0', - }, - }); - - console.log( - `๐Ÿ“ก Response status: ${response.status} ${response.statusText}`, - ); - - console.log( - `๐Ÿ“ก Response content-type: ${response.headers.get('content-type')}`, - ); - - const contentType = response.headers.get('content-type'); - const isJson = contentType && contentType.includes('application/json'); - - if (isJson) { - const responseData = await response.json(); - - if (response.ok) { - console.log('โœ… Success! Response data:'); - console.log(JSON.stringify(responseData, null, 2)); - } else { - console.log('โŒ Error response:'); - console.log(JSON.stringify(responseData, null, 2)); - } - } else { - const responseText = await response.text(); - - if (response.status === 404) { - console.log('โŒ Endpoint not found (404)'); - console.log('๐Ÿ’ก This might mean:'); - console.log(' - The server is not running'); - console.log(' - The endpoint does not exist'); - console.log(' - The API route is not implemented'); - console.log( - `๐Ÿ“„ Response preview: ${responseText.substring(0, 200)}...`, - ); - } else { - console.log(`โŒ Non-JSON response (${response.status})`); - console.log(`๐Ÿ“„ Response: ${responseText.substring(0, 500)}...`); - } - } - } catch (error) { - console.error('โŒ Failed to call endpoint:', error); - - if (error instanceof Error && error.message.includes('ECONNREFUSED')) { - console.log( - '๐Ÿ’ก Connection refused - is the server running on http://localhost:3000?', - ); - } - - throw error; - } -} - -async function cleanup(agentId: string): Promise { - console.log('๐Ÿงน Cleaning up test agent...'); - - try { - await db.delete(agents).where(eq(agents.id, agentId)); - console.log('โœ… Test agent cleaned up'); - } catch (error) { - console.error('โŒ Failed to cleanup agent:', error); - } -} - -async function main() { - const config: TestConfig = { - orgId: process.argv[2] || 'org_2wbhchVXZMQl8OS1yt0mrDazCpW', - agentName: process.argv[3] || 'TestAgent', - baseUrl: process.argv[4] || 'http://localhost:3000', - }; - - console.log('๐Ÿš€ Starting agent endpoint test...'); - console.log(` Org ID: ${config.orgId}`); - console.log(` Agent Name: ${config.agentName}`); - console.log(` Base URL: ${config.baseUrl}`); - console.log(''); - - let agentId: string | null = null; - - try { - agentId = await createTestAgent(config); - const token = await getAgentToken(agentId, config.orgId); - await testEndpoint(token, config.baseUrl); - - console.log(''); - console.log('๐ŸŽ‰ Test completed successfully!'); - } catch (error) { - console.error(''); - console.error('๐Ÿ’ฅ Test failed:', error); - process.exit(1); - } finally { - if (agentId) { - await cleanup(agentId); - } - - await client.end(); - } -} - -// Print usage if help requested -if (process.argv.includes('--help') || process.argv.includes('-h')) { - console.log(` -Usage: tsx scripts/test-agent-endpoint.ts [orgId] [agentName] [baseUrl] - -Arguments: - orgId - Organization ID to test with (default: org_default_test) - agentName - Name for the test agent (default: TestAgent) - baseUrl - Base URL of the API (default: http://localhost:3000) - -Examples: - tsx scripts/test-agent-endpoint.ts - tsx scripts/test-agent-endpoint.ts org_123 MyTestAgent http://localhost:3000 - tsx scripts/test-agent-endpoint.ts org_456 ProductionAgent https://api.example.com - -Environment Variables Required: - DATABASE_URL - PostgreSQL connection string - CLERK_SECRET_KEY - Clerk secret key for JWT signing -`); - process.exit(0); -} - -main().catch((error) => { - console.error('Unhandled error:', error); - process.exit(1); -}); diff --git a/turbo.json b/turbo.json index 424bdf1b65..876ba385e9 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,11 @@ { "$schema": "https://turbo.build/schema.json", + "globalPassThroughEnv": [ + "DOTENV_PRIVATE_KEY_TEST", + "DOTENV_PRIVATE_KEY_DEVELOPMENT", + "DOTENV_PRIVATE_KEY_PREVIEW", + "DOTENV_PRIVATE_KEY_PRODUCTION" + ], "tasks": { "lint": {}, "check-types": {},