diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile new file mode 100644 index 0000000..e9c9759 --- /dev/null +++ b/.cursor/Dockerfile @@ -0,0 +1,35 @@ +# Cursor Cloud Agent base image for deweyjose/graphqlcodegen. +# Pins the toolchain agents need; does NOT COPY the repository (Cursor checks out +# the requested revision separately). Keep this lean — no IDEs, no credentials. +# +# Toolchain pins (must stay in sync with the repo / CI): +# - JDK 17 (java.version in plugin + example poms; actions/setup-java temurin 17) +# - Maven is NOT installed system-wide — agents always use ./mvnw, which downloads +# Apache Maven 3.9.6 (see .mvn/wrapper/maven-wrapper.properties). + +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \ + LANG=C.UTF-8 + +# git + curl: required on fully custom images. python3: offline schemaUrl serving +# for the example harness (see AGENTS.md). unzip: Maven wrapper zip extraction. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + openjdk-17-jdk-headless \ + python3 \ + unzip \ + && rm -rf /var/lib/apt/lists/* \ + && test -x "$JAVA_HOME/bin/java" \ + && "$JAVA_HOME/bin/java" -version 2>&1 | grep -q 'version "17' + +# ubuntu:24.04 already has an `ubuntu` user; do not recreate it. +# Put JDK 17 first on PATH for login shells (bash -lc used by install/start). +ENV PATH="${JAVA_HOME}/bin:/usr/local/bin:/usr/bin:/bin" + +USER ubuntu +WORKDIR /home/ubuntu diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 0000000..df9b981 --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,10 @@ +{ + "name": "graphqlcodegen", + "build": { + "dockerfile": "Dockerfile", + "context": "." + }, + "user": "ubuntu", + "install": "bash .cursor/install.sh", + "start": "bash .cursor/start.sh" +} diff --git a/.cursor/install.sh b/.cursor/install.sh new file mode 100755 index 0000000..da4ada6 --- /dev/null +++ b/.cursor/install.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Cursor Cloud Agent install — runs once per environment build and is snapshotted. +# Prefetch the full reactor's Maven dependencies (plugin + examples) into ~/.m2 so +# agents boot without a cold download. Never bake credentials; never start servers. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +HASH_DIR="${HOME}/.cache/graphqlcodegen-cloud-env" +HASH_FILE="${HASH_DIR}/pom.sha256" +mkdir -p "${HASH_DIR}" + +log() { printf 'graphqlcodegen-cloud-env install: %s\n' "$*"; } + +prefer_java_17_home() { + # Prefer the image/CI JDK when a newer system JDK is also installed. + local candidate="${JAVA_HOME:-}" + if [[ -z "${candidate}" || ! -x "${candidate}/bin/java" ]]; then + for candidate in \ + /usr/lib/jvm/java-17-openjdk-amd64 \ + /usr/lib/jvm/java-17-openjdk \ + /usr/lib/jvm/temurin-17-jdk-amd64; do + if [[ -x "${candidate}/bin/java" ]]; then + export JAVA_HOME="${candidate}" + export PATH="${JAVA_HOME}/bin:${PATH}" + return 0 + fi + done + elif [[ -x "${candidate}/bin/java" ]]; then + export PATH="${candidate}/bin:${PATH}" + fi +} + +require_java_17() { + prefer_java_17_home + if ! command -v java >/dev/null 2>&1; then + echo "error: java not on PATH; expected JDK 17 from the environment image" >&2 + exit 1 + fi + local ver major + ver="$(java -version 2>&1 | head -n 1 || true)" + # Extract major from e.g. openjdk version "17.0.20.1" 2026-08-18 + major="$(java -version 2>&1 | sed -n 's/.*version "\([0-9]*\).*/\1/p' | head -n 1)" + if [[ "${major}" != "17" ]]; then + echo "error: expected Java 17, got: ${ver}" >&2 + echo "hint: JAVA_HOME=${JAVA_HOME:-} PATH=${PATH}" >&2 + exit 1 + fi + log "java OK (${ver})" +} + +# Stable fingerprint of every reactor POM + the Maven wrapper pin. start.sh warns +# when an agent branch drifts from the snapshot that recorded this hash. +record_pom_hash() { + # LC_ALL=C keeps sort order stable across locales. + local fingerprint + fingerprint="$( + { + find . -name pom.xml -print + printf '%s\n' ./.mvn/wrapper/maven-wrapper.properties + } | LC_ALL=C sort -u | while IFS= read -r f; do + # Skip missing optional paths without failing the hash. + if [[ -f "${f}" ]]; then + # Strip leading ./ so sha256sum's path column (and thus the digest) is + # stable regardless of whether find emitted ./pom.xml or pom.xml. + sha256sum "${f#./}" + fi + done | sha256sum | awk '{print $1}' + )" + printf '%s\n' "${fingerprint}" >"${HASH_FILE}" + log "recorded pom hash ${fingerprint} -> ${HASH_FILE}" +} + +prefetch_maven() { + if [[ ! -x ./mvnw ]]; then + echo "error: ./mvnw missing; always use the repo wrapper (Maven 3.9.6)" >&2 + exit 1 + fi + + # First invocation downloads the wrapper's pinned Maven distribution (3.9.6). + log "warming Maven wrapper (./mvnw -v)" + ./mvnw -v + + # Populate ~/.m2 for the whole reactor (plugin + examples). go-offline pulls + # dependency + plugin artifacts; resolve-plugins covers plugin-extension gaps + # go-offline sometimes misses on multi-module reactors. + log "prefetching reactor dependencies (dependency:go-offline)" + ./mvnw -B -ntp dependency:go-offline + + log "resolving plugin artifacts (dependency:resolve-plugins)" + ./mvnw -B -ntp dependency:resolve-plugins + + log "Maven local repository warm at ${HOME}/.m2/repository" +} + +require_java_17 +record_pom_hash +prefetch_maven +log "done" diff --git a/.cursor/start.sh b/.cursor/start.sh new file mode 100755 index 0000000..f90b486 --- /dev/null +++ b/.cursor/start.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Cursor Cloud Agent start — runs detached on every agent boot. +# This repo is a Maven plugin + example harness: no long-running services to +# launch. Only warn when the snapshotted pom fingerprint looks stale. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +HASH_DIR="${HOME}/.cache/graphqlcodegen-cloud-env" +HASH_FILE="${HASH_DIR}/pom.sha256" + +log() { printf 'graphqlcodegen-cloud-env start: %s\n' "$*"; } + +current_pom_hash() { + { + find . -name pom.xml -print + printf '%s\n' ./.mvn/wrapper/maven-wrapper.properties + } | LC_ALL=C sort -u | while IFS= read -r f; do + if [[ -f "${f}" ]]; then + # Strip leading ./ so sha256sum's path column (and thus the digest) is + # stable regardless of whether find emitted ./pom.xml or pom.xml. + sha256sum "${f#./}" + fi + done | sha256sum | awk '{print $1}' +} + +if [[ -f "${HASH_FILE}" ]]; then + expected="$(tr -d '[:space:]' <"${HASH_FILE}")" + actual="$(current_pom_hash)" + if [[ "${expected}" != "${actual}" ]]; then + log "WARNING: pom/wrapper fingerprint changed since the environment snapshot." + log " snapshot=${expected}" + log " current =${actual}" + log " Dependency versions may have drifted; re-run: bash .cursor/install.sh" + else + log "pom hash matches snapshot (${actual})" + fi +else + log "WARNING: no pom hash at ${HASH_FILE} (install did not record one)." + log " Run: bash .cursor/install.sh" +fi + +# Intentionally empty of services — agents start Maven themselves via ./mvnw. +log "no runtime services to start" +exit 0 diff --git a/AGENTS.md b/AGENTS.md index e5db834..e764441 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ encodes the architecture, the conventions CI enforces, and the gotchas that are ## Doc map -- **This file** — repo-wide: reactor layout, build/test commands, CI, release. +- **This file** — repo-wide: reactor layout, build/test commands, CI, release, Cloud Agent env. - [`graphqlcodegen-maven-plugin/AGENTS.md`](graphqlcodegen-maven-plugin/AGENTS.md) — the plugin's code: request flow, services, the option-wiring checklist, module-level gotchas. - [`examples/graphqlcodegen-example/AGENTS.md`](examples/graphqlcodegen-example/AGENTS.md) — the @@ -13,6 +13,7 @@ encodes the architecture, the conventions CI enforces, and the gotchas that are - [`README.md`](README.md) — the **consumer-facing option reference**. Its documented types and defaults must match the `@Parameter` declarations in `Codegen.java`; keep it in sync whenever options change. +- [`.cursor/`](.cursor/) — Cursor Cloud Agent environment (Dockerfile + install/start scripts). ## What this project is @@ -30,6 +31,30 @@ and clients from `.graphqls` schemas during the build. to the `generate-sources` phase. - Consumers configure it entirely through Maven `` parameters. +## Cloud Agent environment + +Cursor Cloud Agents boot from a prebuilt environment snapshot so they skip cold JDK/Maven +downloads. Configuration lives under [`.cursor/`](.cursor/): + +| File | Role | +| --- | --- | +| [`environment.json`](.cursor/environment.json) | Wires the Dockerfile + `install` / `start` scripts | +| [`Dockerfile`](.cursor/Dockerfile) | Lean Ubuntu 24.04 image with **JDK 17** only (no system Maven) | +| [`install.sh`](.cursor/install.sh) | One-shot dependency prefetch (`./mvnw dependency:go-offline` + `resolve-plugins`) and pom hash | +| [`start.sh`](.cursor/start.sh) | Minimal boot: warn if the snapshotted pom/wrapper hash is stale; no services | + +**Pins (keep in sync when bumping toolchains):** + +- **JDK 17** — `java.version` in the plugin and example poms; CI uses Temurin 17 / + `openjdk-17` in the image. +- **Maven 3.9.6** via `./mvnw` — see `.mvn/wrapper/maven-wrapper.properties` + (`wrapperVersion=3.3.4`). Never install or invoke a system `mvn`. + +`install` is snapshotted; `start` runs on every agent boot and must stay cheap. Do not put +credentials in these files. After changing poms or the wrapper on a branch that diverges from +the snapshot, re-run `bash .cursor/install.sh` (or ignore the start warning and let Maven +refresh what changed). + ## Build, test, format Always use the Maven wrapper (`./mvnw`), never a system `mvn` — it pins the build version.