diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile new file mode 100644 index 0000000..58f6c26 --- /dev/null +++ b/.cursor/Dockerfile @@ -0,0 +1,32 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + gnupg \ + jq \ + openssh-server \ + software-properties-common \ + sudo \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +RUN add-apt-repository -y ppa:ondrej/php \ + && apt-get update && apt-get install -y --no-install-recommends \ + php8.5-bcmath \ + php8.5-cli \ + php8.5-curl \ + php8.5-intl \ + php8.5-mbstring \ + php8.5-mysql \ + php8.5-sqlite3 \ + php8.5-xml \ + php8.5-zip \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +WORKDIR /home/ubuntu diff --git a/.cursor/cloud-agent-install.sh b/.cursor/cloud-agent-install.sh new file mode 100755 index 0000000..1c7a81d --- /dev/null +++ b/.cursor/cloud-agent-install.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +composer install --no-interaction + +if [[ ! -f .env ]]; then + cp .env.example .env + php artisan key:generate --no-interaction +fi + +if [[ ! -f database/database.sqlite ]]; then + touch database/database.sqlite +fi + +php artisan migrate --no-interaction diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 0000000..a3f649f --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,15 @@ +{ + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "install": "bash .cursor/cloud-agent-install.sh", + "start": "php artisan serve --host=0.0.0.0 --port=8000", + "terminals": [ + { + "name": "App", + "command": "php artisan serve --host=0.0.0.0 --port=8000", + "ports": [8000] + } + ] +} diff --git a/.cursor/test-cloud-environment.sh b/.cursor/test-cloud-environment.sh new file mode 100755 index 0000000..9ae1f61 --- /dev/null +++ b/.cursor/test-cloud-environment.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +pass() { + echo "cloud-env-test: ok $*" +} + +fail() { + echo "cloud-env-test: FAIL $*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "missing command: $1" +} + +require_php_extension() { + php -m | grep -qi "^$1$" || fail "missing php extension: $1" +} + +echo "cloud-env-test: validating Cursor Cloud image prerequisites" + +require_command php +require_command composer +require_command git +require_command sudo +require_command jq +require_command bash + +php -v | grep -q 'PHP 8.5' || fail "expected PHP 8.5" + +for extension in bcmath curl intl mbstring pdo_sqlite xml zip; do + require_php_extension "$extension" +done + +[[ -d /etc/ssh/sshd_config.d ]] || fail "missing /etc/ssh/sshd_config.d (install openssh-server)" +[[ -d /etc/sudoers.d ]] || fail "missing /etc/sudoers.d (install sudo)" + +jq empty .cursor/environment.json || fail "invalid .cursor/environment.json" +[[ -f .cursor/Dockerfile ]] || fail "missing .cursor/Dockerfile" +[[ -x .cursor/hooks/php-quality.sh ]] || fail "php-quality hook is not executable" + +pass "image prerequisites" + +echo "cloud-env-test: running cloud agent install" + +bash .cursor/cloud-agent-install.sh + +[[ -f vendor/autoload.php ]] || fail "composer dependencies were not installed" +[[ -x vendor/bin/pest ]] || fail "pest binary missing after install" +[[ -x vendor/bin/pint ]] || fail "pint binary missing after install" +[[ -x vendor/bin/phpstan ]] || fail "phpstan binary missing after install" +[[ -x vendor/bin/rector ]] || fail "rector binary missing after install" + +php artisan --version >/dev/null || fail "artisan is not runnable" +[[ -f .env ]] || fail ".env was not created" +[[ -f database/database.sqlite ]] || fail "sqlite database file was not created" + +pass "application install" + +echo "cloud-env-test: running php-quality hook smoke test" + +hook_input=$( + jq -n \ + --arg file_path "app/Http/Controllers/ProfileController.php" \ + --arg workspace_root "$ROOT" \ + '{file_path: $file_path, workspace_roots: [$workspace_root]}' +) + +echo "$hook_input" | bash .cursor/hooks/php-quality.sh + +pass "php-quality hook" + +echo "cloud-env-test: running pest suite" + +php artisan test --compact + +pass "all cloud environment checks passed" diff --git a/.github/workflows/cloud-environment.yml b/.github/workflows/cloud-environment.yml new file mode 100644 index 0000000..b91416a --- /dev/null +++ b/.github/workflows/cloud-environment.yml @@ -0,0 +1,35 @@ +name: Cloud environment + +on: + pull_request: + paths: + - '.cursor/**' + - 'composer.json' + - 'composer.lock' + - '.github/workflows/cloud-environment.yml' + push: + branches: + - main + paths: + - '.cursor/**' + - 'composer.json' + - 'composer.lock' + - '.github/workflows/cloud-environment.yml' + +permissions: + contents: read + +jobs: + cloud_environment: + name: Cursor Cloud Docker image + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Build Cursor Cloud image + run: docker build -f .cursor/Dockerfile -t kaper-dev-cloud . + + - name: Validate cloud environment + run: docker run --rm -v "$PWD:/workspace" -w /workspace kaper-dev-cloud bash .cursor/test-cloud-environment.sh diff --git a/.github/workflows/composer-normalize.yml b/.github/workflows/composer-normalize.yml index 50d91a3..e125d7d 100644 --- a/.github/workflows/composer-normalize.yml +++ b/.github/workflows/composer-normalize.yml @@ -1,6 +1,10 @@ name: Composer Normalize -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read diff --git a/.github/workflows/pest.yml b/.github/workflows/pest.yml index 03a044b..02b4b7c 100644 --- a/.github/workflows/pest.yml +++ b/.github/workflows/pest.yml @@ -1,6 +1,10 @@ name: Pest tests -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index e507097..87108ad 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -1,6 +1,10 @@ name: PHPStan -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index 843b138..88803dd 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -1,6 +1,10 @@ name: Pint -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read diff --git a/.github/workflows/rector.yaml b/.github/workflows/rector.yaml index 123761f..ccc25d1 100644 --- a/.github/workflows/rector.yaml +++ b/.github/workflows/rector.yaml @@ -1,6 +1,10 @@ name: Rector -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read