From 621b4fe9999fa544f1d701f1f3cd07b0d3576975 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 4 Oct 2026 20:34:07 +0000 Subject: [PATCH 1/2] chore: add Cursor Cloud Agent development environment Teach cloud agents how to install Plane and boot the local stack: Node 22.22.0, pnpm 11.10.0, Docker, and the web, admin, space, live, and API dev servers. --- .cursor/Dockerfile | 83 +++++++++++++++++++++++++++++++++ .cursor/common.sh | 99 ++++++++++++++++++++++++++++++++++++++++ .cursor/environment.json | 32 +++++++++++++ .cursor/install.sh | 50 ++++++++++++++++++++ .cursor/logs-api.sh | 18 ++++++++ .cursor/run-dev.sh | 19 ++++++++ .cursor/start.sh | 25 ++++++++++ AGENTS.md | 6 +++ 8 files changed, 332 insertions(+) create mode 100644 .cursor/Dockerfile create mode 100755 .cursor/common.sh create mode 100644 .cursor/environment.json create mode 100755 .cursor/install.sh create mode 100755 .cursor/logs-api.sh create mode 100755 .cursor/run-dev.sh create mode 100755 .cursor/start.sh diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile new file mode 100644 index 000000000000..82434e7fc9dd --- /dev/null +++ b/.cursor/Dockerfile @@ -0,0 +1,83 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ + COREPACK_DEFAULT_TO_LATEST=0 \ + COREPACK_HOME=/usr/local/share/corepack \ + PATH="/usr/local/bin:${PATH}" \ + BASH_ENV="/etc/profile.d/99-plane-path.sh" + +# git and curl are required by Cloud Agents. python3 and build tools cover +# native Node addons (esbuild, swc, msgpackr) when prebuilds are unavailable. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + gnupg \ + sudo \ + xz-utils \ + python3 \ + build-essential \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +# Matches .node-version and package.json engines.node (>=22.22.0). +# Installed under /usr/local/bin so login shells find it without rc files. +ARG NODE_VERSION=22.22.0 +RUN curl --retry 3 --retry-delay 5 -fsSL \ + "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + | tar -xJ -C /usr/local --strip-components=1 --no-same-owner \ + && mkdir -p /usr/local/share/corepack \ + && corepack enable \ + && corepack prepare pnpm@11.10.0 --activate \ + && chmod -R a+rX /usr/local/share/corepack + +RUN printf '%s\n' 'export PATH="/usr/local/bin:${PATH}"' > /etc/profile.d/99-plane-path.sh \ + && printf '%s\n%s\n' 'export PATH="/usr/local/bin:${PATH}"' "$(cat /etc/bash.bashrc)" > /etc/bash.bashrc + +######################################################## +# DOCKER INSTALLATION +# https://cursor.com/docs/cloud-agent/setup#running-docker +######################################################## + +RUN install -m 0755 -d /etc/apt/keyrings \ + && curl --retry 3 --retry-delay 5 -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && chmod a+r /etc/apt/keyrings/docker.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt-get update \ + && apt-get install -y \ + docker-ce=5:28.5.2-1~ubuntu.24.04~noble \ + docker-ce-cli=5:28.5.2-1~ubuntu.24.04~noble \ + docker-ce-rootless-extras=5:28.5.2-1~ubuntu.24.04~noble \ + containerd.io \ + docker-buildx-plugin \ + docker-compose-plugin \ + && rm -rf /var/lib/apt/lists/* + +RUN apt-get update \ + && apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" fuse-overlayfs \ + && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /etc/docker \ + && printf '%s\n' '{' \ + ' "storage-driver": "fuse-overlayfs"' \ + '}' > /etc/docker/daemon.json +RUN apt-get update && apt-get install -y iptables && rm -rf /var/lib/apt/lists/* +RUN update-alternatives --set iptables /usr/sbin/iptables-legacy \ + && update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy + +# ubuntu:24.04 already has the ubuntu user. Do not useradd it. +# The base image does not install OpenSSH, so create the drop-in directory first. +RUN mkdir -p /etc/ssh/sshd_config.d \ + && printf '%s\n' 'PasswordAuthentication no' 'ChallengeResponseAuthentication no' 'UsePAM no' > /etc/ssh/sshd_config.d/disable_password_auth.conf \ + && groupadd -f docker \ + && usermod -aG docker ubuntu \ + && usermod -aG sudo ubuntu \ + && echo "ubuntu ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ubuntu \ + && chmod 0440 /etc/sudoers.d/ubuntu \ + && chmod 1777 /usr/local/share/corepack + +USER ubuntu +WORKDIR /workspace diff --git a/.cursor/common.sh b/.cursor/common.sh new file mode 100755 index 000000000000..231a68850cbf --- /dev/null +++ b/.cursor/common.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Shared helpers for the Cloud Agent install and start scripts. + +export PATH="/usr/local/bin:${PATH}" +export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 +export COREPACK_DEFAULT_TO_LATEST=0 +export COREPACK_HOME="${COREPACK_HOME:-/usr/local/share/corepack}" + +# Resolve once, at source time, so later cdirs do not reinterpret a relative path. +_PLANE_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_PLANE_ROOT="$(cd "${_PLANE_COMMON_DIR}/.." && pwd)" + +plane_root() { + printf '%s\n' "$_PLANE_ROOT" +} + +copy_env_if_missing() { + local source=$1 + local destination=$2 + if [ -f "$destination" ]; then + echo "env exists: $destination" + return 0 + fi + if [ ! -f "$source" ]; then + echo "missing env example: $source" >&2 + return 1 + fi + cp "$source" "$destination" + echo "copied $destination" +} + +ensure_secret_key() { + local env_file="apps/api/.env" + if [ ! -f "$env_file" ]; then + echo "apps/api/.env is missing; cannot add SECRET_KEY" >&2 + return 1 + fi + if grep -q '^SECRET_KEY=' "$env_file"; then + echo "SECRET_KEY already set" + return 0 + fi + local secret_key + # Do not pipe /dev/urandom into head: with pipefail, head closing the pipe + # exits the script with SIGPIPE (141) before the key is written. + secret_key="$(python3 -c 'import secrets,string; alphabet=string.ascii_lowercase+string.digits; print("".join(secrets.choice(alphabet) for _ in range(50)), end="")')" + if [ -z "$secret_key" ]; then + echo "failed to generate SECRET_KEY" >&2 + return 1 + fi + printf '\nSECRET_KEY="%s"\n' "$secret_key" >> "$env_file" + echo "added SECRET_KEY to apps/api/.env" +} + +ensure_docker() { + if docker info >/dev/null 2>&1; then + return 0 + fi + echo "starting docker daemon" + sudo service docker start >/tmp/docker-service.log 2>&1 || true + local attempt + for attempt in $(seq 1 45); do + if [ -S /var/run/docker.sock ]; then + sudo chmod 666 /var/run/docker.sock 2>/dev/null || true + fi + if docker info >/dev/null 2>&1; then + return 0 + fi + sleep 1 + done + echo "service docker did not become ready; starting dockerd directly" >&2 + sudo dockerd >/tmp/dockerd.log 2>&1 & + for attempt in $(seq 1 45); do + if [ -S /var/run/docker.sock ]; then + sudo chmod 666 /var/run/docker.sock 2>/dev/null || true + fi + if docker info >/dev/null 2>&1; then + return 0 + fi + sleep 1 + done + echo "Docker daemon failed to start. See /tmp/docker-service.log and /tmp/dockerd.log" >&2 + return 1 +} + +wait_for_http() { + local url=$1 + local attempts=${2:-180} + local attempt code + for attempt in $(seq 1 "$attempts"); do + code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 "$url" || true)" + if [ -n "$code" ] && [ "$code" != "000" ]; then + echo "ready $url ($code)" + return 0 + fi + sleep 2 + done + echo "timed out waiting for $url" >&2 + return 1 +} diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 000000000000..8e2411491efd --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,32 @@ +{ + "name": "Plane", + "user": "ubuntu", + "build": { + "dockerfile": "Dockerfile" + }, + "install": "bash .cursor/install.sh", + "start": "bash .cursor/start.sh", + "terminals": [ + { + "name": "dev", + "command": "bash .cursor/run-dev.sh", + "description": "pnpm dev: web :3000, admin :3001, space :3002, live :3100" + }, + { + "name": "api", + "command": "bash .cursor/logs-api.sh", + "description": "Docker logs for the Django API, worker, and beat" + } + ], + "ports": [ + { "name": "web", "port": 3000 }, + { "name": "admin", "port": 3001 }, + { "name": "space", "port": 3002 }, + { "name": "live", "port": 3100 }, + { "name": "api", "port": 8000 }, + { "name": "postgres", "port": 5432 }, + { "name": "redis", "port": 6379 }, + { "name": "minio", "port": 9000 }, + { "name": "minio console", "port": 9090 } + ] +} diff --git a/.cursor/install.sh b/.cursor/install.sh new file mode 100755 index 000000000000..d0ef160130a3 --- /dev/null +++ b/.cursor/install.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Idempotent repository bootstrap for Cloud Agents. +# Toolchain (Node, pnpm, Docker) comes from .cursor/Dockerfile. +# This script only refreshes env files, JS dependencies, and local images. +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" +cd "$(plane_root)" + +/usr/local/bin/node -e ' +const [major, minor] = process.versions.node.split(".").map(Number); +if (major < 22 || (major === 22 && minor < 22)) { + console.error(`Node ${process.versions.node} is older than 22.22.0`); + process.exit(1); +} +console.log(`node v${process.versions.node}`); +' + +copy_env_if_missing ".env.example" ".env" +copy_env_if_missing "apps/web/.env.example" "apps/web/.env" +copy_env_if_missing "apps/api/.env.example" "apps/api/.env" +copy_env_if_missing "apps/space/.env.example" "apps/space/.env" +copy_env_if_missing "apps/admin/.env.example" "apps/admin/.env" +copy_env_if_missing "apps/live/.env.example" "apps/live/.env" +ensure_secret_key + +# packageManager pins pnpm@11.10.0. Activate it if the image shim is stale. +if ! command -v pnpm >/dev/null 2>&1 || [ "$(pnpm -v 2>/dev/null || true)" != "11.10.0" ]; then + sudo mkdir -p /usr/local/share/corepack + sudo chmod 1777 /usr/local/share/corepack + sudo /usr/local/bin/corepack enable + # Prepare as the runtime user so the package is not stored only in root's home. + /usr/local/bin/corepack prepare pnpm@11.10.0 --activate +fi +echo "pnpm $(pnpm -v)" + +pnpm install --frozen-lockfile + +ensure_docker + +echo "pulling local infrastructure images" +docker compose -f docker-compose-local.yml pull plane-db plane-redis plane-mq plane-minio + +# api, worker, beat-worker, and migrator share Dockerfile.dev. +# Build api first so the other services reuse the layer cache. +echo "building local API image" +docker compose -f docker-compose-local.yml build api +docker compose -f docker-compose-local.yml build worker beat-worker migrator + +echo "install complete" diff --git a/.cursor/logs-api.sh b/.cursor/logs-api.sh new file mode 100755 index 000000000000..7ab2577667f9 --- /dev/null +++ b/.cursor/logs-api.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Follow API container logs once compose has created the api service. +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" +cd "$(plane_root)" + +attempt=0 +until docker compose -f docker-compose-local.yml ps -q api | grep -q .; do + attempt=$((attempt + 1)) + if [ "$attempt" -gt 180 ]; then + echo "api container was not created" >&2 + exit 1 + fi + sleep 2 +done + +exec docker compose -f docker-compose-local.yml logs -f api worker beat-worker diff --git a/.cursor/run-dev.sh b/.cursor/run-dev.sh new file mode 100755 index 000000000000..2cce8a6b98f6 --- /dev/null +++ b/.cursor/run-dev.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# pnpm dev for the web, admin, space, and live apps, plus workspace package watchers. +# Terminals start beside the boot script, so wait until the API is accepting connections. +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" +cd "$(plane_root)" + +if curl -sf -o /dev/null --max-time 2 http://127.0.0.1:3000/ \ + && curl -sf -o /dev/null --max-time 2 http://127.0.0.1:3001/; then + echo "dev servers already listening on :3000 and :3001" + exec sleep infinity +fi + +if ! wait_for_http "http://127.0.0.1:8000/api/instances/" 180; then + echo "API was not ready; starting pnpm dev anyway" >&2 +fi + +exec pnpm dev diff --git a/.cursor/start.sh b/.cursor/start.sh new file mode 100755 index 000000000000..4c2fd71fcc9e --- /dev/null +++ b/.cursor/start.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Per-boot services. Must be safe to run more than once. +# Dev servers are launched from terminals (.cursor/run-dev.sh), not here. +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" +cd "$(plane_root)" + +ensure_docker + +if [ ! -f apps/api/.env ] || [ ! -f .env ]; then + echo "env files missing; run bash .cursor/install.sh first" >&2 + exit 1 +fi + +docker compose -f docker-compose-local.yml up -d + +echo "waiting for Plane API on :8000" +if ! wait_for_http "http://127.0.0.1:8000/api/instances/" 180; then + docker compose -f docker-compose-local.yml ps >&2 || true + docker compose -f docker-compose-local.yml logs --tail 120 api migrator >&2 || true + exit 1 +fi + +echo "local stack is up" diff --git a/AGENTS.md b/AGENTS.md index 5dabb62afab4..342ea63894c1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,12 @@ - `pnpm fix` - Auto-fix format and lint issues - `pnpm turbo run --filter=` - Target specific package/app +## Cloud Agent + +`.cursor/environment.json` is the Cloud Agent environment. The image supplies Node 22.22.0, pnpm 11.10.0, and Docker. `bash .cursor/install.sh` copies any missing `.env` files, adds a Django `SECRET_KEY` when `apps/api/.env` does not have one, runs `pnpm install --frozen-lockfile`, and builds the local API image. `bash .cursor/start.sh` starts Docker and `docker compose -f docker-compose-local.yml up -d` (Postgres, Valkey, RabbitMQ, MinIO, API on :8000). The `dev` terminal runs `pnpm dev` (web :3000, admin :3001, space :3002, live :3100). + +Do not re-run `./setup.sh` on a checkout that already has `.env` files. It overwrites those files and appends another `SECRET_KEY`. + ## Code Style - **Imports**: Use `workspace:*` for internal packages, `catalog:` for external deps From 370db9a363001a2b436340ad57c0c75fc7ad4bdf Mon Sep 17 00:00:00 2001 From: Vihar Kurama Date: Mon, 5 Oct 2026 12:41:40 +0000 Subject: [PATCH 2/2] chore: align Cloud Agent setup with CONTRIBUTING Keep the image to Node, pnpm, and Docker. install.sh is a safe setup.sh plus compose image pulls, and start.sh is docker compose up -d for the local API. Frontends stay on pnpm dev. --- .cursor/Dockerfile | 16 +++++++++------- .cursor/common.sh | 7 ++++--- .cursor/environment.json | 12 ++++++------ .cursor/install.sh | 16 ++++++++++++---- .cursor/{logs-api.sh => logs-backend.sh} | 3 ++- .cursor/{run-dev.sh => run-frontend.sh} | 3 ++- .cursor/start.sh | 10 ++++++++-- AGENTS.md | 10 ++++++++-- 8 files changed, 51 insertions(+), 26 deletions(-) rename .cursor/{logs-api.sh => logs-backend.sh} (75%) rename .cursor/{run-dev.sh => run-frontend.sh} (79%) diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile index 82434e7fc9dd..cae9ef18b192 100644 --- a/.cursor/Dockerfile +++ b/.cursor/Dockerfile @@ -1,14 +1,19 @@ FROM ubuntu:24.04 +# Thin Cloud Agent toolchain only: Node (matches .node-version), pnpm, and Docker. +# Plane is not installed or booted in this image. Contributors start it with +# ./setup.sh, docker compose -f docker-compose-local.yml, and pnpm dev +# (see CONTRIBUTING.md). Cloud Agents run the safe equivalents in +# .cursor/install.sh and .cursor/start.sh. + ENV DEBIAN_FRONTEND=noninteractive \ COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ COREPACK_DEFAULT_TO_LATEST=0 \ COREPACK_HOME=/usr/local/share/corepack \ PATH="/usr/local/bin:${PATH}" \ - BASH_ENV="/etc/profile.d/99-plane-path.sh" + BASH_ENV="/etc/profile.d/99-local-bin-path.sh" -# git and curl are required by Cloud Agents. python3 and build tools cover -# native Node addons (esbuild, swc, msgpackr) when prebuilds are unavailable. +# git and curl are required by Cloud Agents. RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ @@ -17,9 +22,6 @@ RUN apt-get update \ gnupg \ sudo \ xz-utils \ - python3 \ - build-essential \ - pkg-config \ && rm -rf /var/lib/apt/lists/* # Matches .node-version and package.json engines.node (>=22.22.0). @@ -33,7 +35,7 @@ RUN curl --retry 3 --retry-delay 5 -fsSL \ && corepack prepare pnpm@11.10.0 --activate \ && chmod -R a+rX /usr/local/share/corepack -RUN printf '%s\n' 'export PATH="/usr/local/bin:${PATH}"' > /etc/profile.d/99-plane-path.sh \ +RUN printf '%s\n' 'export PATH="/usr/local/bin:${PATH}"' > /etc/profile.d/99-local-bin-path.sh \ && printf '%s\n%s\n' 'export PATH="/usr/local/bin:${PATH}"' "$(cat /etc/bash.bashrc)" > /etc/bash.bashrc ######################################################## diff --git a/.cursor/common.sh b/.cursor/common.sh index 231a68850cbf..ffff2b11ba06 100755 --- a/.cursor/common.sh +++ b/.cursor/common.sh @@ -40,9 +40,10 @@ ensure_secret_key() { return 0 fi local secret_key - # Do not pipe /dev/urandom into head: with pipefail, head closing the pipe - # exits the script with SIGPIPE (141) before the key is written. - secret_key="$(python3 -c 'import secrets,string; alphabet=string.ascii_lowercase+string.digits; print("".join(secrets.choice(alphabet) for _ in range(50)), end="")')" + # Same alphabet as setup.sh (a-z0-9, 50 chars). Generate with node so the + # image does not need Python. Do not pipe /dev/urandom into head: with + # pipefail, head closing the pipe exits 141 before the key is written. + secret_key="$(node -e 'const {randomInt}=require("crypto"); const alphabet="abcdefghijklmnopqrstuvwxyz0123456789"; let secret=""; for (let i=0;i<50;i++) secret+=alphabet[randomInt(alphabet.length)]; process.stdout.write(secret);')" if [ -z "$secret_key" ]; then echo "failed to generate SECRET_KEY" >&2 return 1 diff --git a/.cursor/environment.json b/.cursor/environment.json index 8e2411491efd..c3daad899b47 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -8,14 +8,14 @@ "start": "bash .cursor/start.sh", "terminals": [ { - "name": "dev", - "command": "bash .cursor/run-dev.sh", - "description": "pnpm dev: web :3000, admin :3001, space :3002, live :3100" + "name": "backend", + "command": "bash .cursor/logs-backend.sh", + "description": "Compose logs for the local API, worker, and beat" }, { - "name": "api", - "command": "bash .cursor/logs-api.sh", - "description": "Docker logs for the Django API, worker, and beat" + "name": "frontend", + "command": "bash .cursor/run-frontend.sh", + "description": "pnpm dev: web :3000, admin :3001, space :3002, live :3100" } ], "ports": [ diff --git a/.cursor/install.sh b/.cursor/install.sh index d0ef160130a3..c71ccd58f62a 100755 --- a/.cursor/install.sh +++ b/.cursor/install.sh @@ -1,7 +1,12 @@ #!/usr/bin/env bash -# Idempotent repository bootstrap for Cloud Agents. -# Toolchain (Node, pnpm, Docker) comes from .cursor/Dockerfile. -# This script only refreshes env files, JS dependencies, and local images. +# Safe equivalent of ./setup.sh for Cloud Agents, plus the image pulls and +# builds docker-compose-local.yml needs before `up`. +# +# Unlike ./setup.sh, this never overwrites an existing .env file and never +# appends a second SECRET_KEY. +# +# When stack.sh lands in the repo, call it from here instead of the steps below. +# Do not add a second way to boot Plane. set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/common.sh" @@ -16,6 +21,7 @@ if (major < 22 || (major === 22 && minor < 22)) { console.log(`node v${process.versions.node}`); ' +# setup.sh copies these six files. Copy only when the destination is missing. copy_env_if_missing ".env.example" ".env" copy_env_if_missing "apps/web/.env.example" "apps/web/.env" copy_env_if_missing "apps/api/.env.example" "apps/api/.env" @@ -36,12 +42,14 @@ echo "pnpm $(pnpm -v)" pnpm install --frozen-lockfile +# CONTRIBUTING assumes Docker is already running. Cloud Agent VMs start the +# daemon here so the image pulls below can run during install. ensure_docker echo "pulling local infrastructure images" docker compose -f docker-compose-local.yml pull plane-db plane-redis plane-mq plane-minio -# api, worker, beat-worker, and migrator share Dockerfile.dev. +# api, worker, beat-worker, and migrator share apps/api/Dockerfile.dev. # Build api first so the other services reuse the layer cache. echo "building local API image" docker compose -f docker-compose-local.yml build api diff --git a/.cursor/logs-api.sh b/.cursor/logs-backend.sh similarity index 75% rename from .cursor/logs-api.sh rename to .cursor/logs-backend.sh index 7ab2577667f9..fdd35bb98b30 100755 --- a/.cursor/logs-api.sh +++ b/.cursor/logs-backend.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Follow API container logs once compose has created the api service. +# Backend terminal. Follow compose logs for the local API once start.sh has +# created the containers. This does not start the stack. set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/common.sh" diff --git a/.cursor/run-dev.sh b/.cursor/run-frontend.sh similarity index 79% rename from .cursor/run-dev.sh rename to .cursor/run-frontend.sh index 2cce8a6b98f6..54c9104ad21a 100755 --- a/.cursor/run-dev.sh +++ b/.cursor/run-frontend.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# pnpm dev for the web, admin, space, and live apps, plus workspace package watchers. +# Frontend terminal. CONTRIBUTING starts the web apps with `pnpm dev` +# (web :3000, admin :3001, space :3002, live :3100), separate from compose. # Terminals start beside the boot script, so wait until the API is accepting connections. set -euo pipefail diff --git a/.cursor/start.sh b/.cursor/start.sh index 4c2fd71fcc9e..9f3497884520 100755 --- a/.cursor/start.sh +++ b/.cursor/start.sh @@ -1,11 +1,17 @@ #!/usr/bin/env bash -# Per-boot services. Must be safe to run more than once. -# Dev servers are launched from terminals (.cursor/run-dev.sh), not here. +# Backend start from CONTRIBUTING.md: +# docker compose -f docker-compose-local.yml up -d +# Then wait until the API answers on :8000. +# +# Frontends stay out of this script. A terminal runs `pnpm dev`. +# When stack.sh lands in the repo, call it from here instead. set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/common.sh" cd "$(plane_root)" +# Cloud Agent VMs have no running Docker daemon until this starts it. +# The Plane stack itself is only the compose file below. ensure_docker if [ ! -f apps/api/.env ] || [ ! -f .env ]; then diff --git a/AGENTS.md b/AGENTS.md index 342ea63894c1..5cae6ce82bd8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Commands -- `pnpm dev` - Start all dev servers (web:3000, admin:3001) +- `pnpm dev` - Start frontend dev servers (web:3000, admin:3001, space:3002, live:3100) - `pnpm build` - Build all packages and apps - `pnpm check` - Run all checks (format, lint, types) - `pnpm check:lint` - OxLint across all packages @@ -12,7 +12,13 @@ ## Cloud Agent -`.cursor/environment.json` is the Cloud Agent environment. The image supplies Node 22.22.0, pnpm 11.10.0, and Docker. `bash .cursor/install.sh` copies any missing `.env` files, adds a Django `SECRET_KEY` when `apps/api/.env` does not have one, runs `pnpm install --frozen-lockfile`, and builds the local API image. `bash .cursor/start.sh` starts Docker and `docker compose -f docker-compose-local.yml up -d` (Postgres, Valkey, RabbitMQ, MinIO, API on :8000). The `dev` terminal runs `pnpm dev` (web :3000, admin :3001, space :3002, live :3100). +`.cursor/environment.json` follows the local setup in CONTRIBUTING.md. The image is only the Cloud Agent toolchain: Node 22.22.0 (`.node-version`), pnpm 11.10.0, and Docker with fuse-overlayfs. It does not install Plane or boot the app. + +`bash .cursor/install.sh` is a safe `./setup.sh`. It copies missing `.env` files from the examples, adds `SECRET_KEY` to `apps/api/.env` only when that line is absent, and runs `pnpm install --frozen-lockfile`. It also pulls and builds the images `docker-compose-local.yml` needs. It never overwrites an existing env file and never appends a second `SECRET_KEY`. + +`bash .cursor/start.sh` runs `docker compose -f docker-compose-local.yml up -d` and waits until the API is healthy on :8000. The `frontend` terminal runs `pnpm dev` (web :3000, admin :3001, space :3002, live :3100). The `backend` terminal follows compose logs for the API, worker, and beat. + +When `stack.sh` lands in the repo, install and start should call that script instead of these compose and pnpm steps. Do not re-run `./setup.sh` on a checkout that already has `.env` files. It overwrites those files and appends another `SECRET_KEY`.