diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0d9ae9d..44eb019 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -48,3 +48,64 @@ jobs: go test ./... - name: Vulnerability scan run: go run golang.org/x/vuln/cmd/govulncheck@latest ./... + + # Scaffold a real full-stack project, install it, set up the DB and hit the + # /health-check endpoint. Catches runtime breakage that `go test` can't (e.g. + # a bad DATABASE_URL that only fails when the app actually opens the DB). + scaffold-smoke: + if: | + (github.event.action == 'review_requested' || + (github.event.action == 'labeled' && github.event.label.name == 'ci-testing') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'ci-testing'))) || + (github.event_name == 'push') + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # sqlite needs no Docker service, so both ORMs run on the cheap runner. + - orm: drizzle + setup_db: pnpm run db:generate && pnpm run db:migrate + - orm: prisma + # `prisma migrate dev` is interactive; `db push` syncs the schema + # non-interactively for the smoke test. + setup_db: pnpm run db:generate && pnpm exec prisma db push + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: actions/setup-go@v5 + with: + go-version: "1.25.x" + - uses: actions/setup-node@v4 + with: + node-version: "22" + - uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Build CLI + run: go build -o bungkus-cli . + - name: Scaffold ${{ matrix.orm }} + sqlite + run: ./bungkus-cli create app --base astro-react --backend hono --orm ${{ matrix.orm }} --db sqlite --layout flat --pm pnpm --git=false + - name: Install, set up DB, seed + working-directory: app + run: | + cp .env.example .env + pnpm install + ${{ matrix.setup_db }} + pnpm run db:seed + - name: Boot API and verify /health-check + working-directory: app + run: | + pnpm run dev:server > server.log 2>&1 & + for i in $(seq 1 30); do + body=$(curl -s http://localhost:8000/health-check || true) + if echo "$body" | grep -q '"status":"ok"'; then + echo "health-check: $body" + echo "$body" | grep -q '"db":"connected"' || { echo "FAIL: db not connected"; exit 1; } + echo "$body" | grep -q 'Ada Lovelace' || { echo "FAIL: seed rows missing"; exit 1; } + exit 0 + fi + sleep 2 + done + echo "FAIL: /health-check never came up"; cat server.log; exit 1 diff --git a/README.md b/README.md index 03011d5..aeaa072 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,21 @@ my-app/ domain/ # shared contract: zod schemas (with --validation zod) or plain types ``` -- **`--backend hono`** runs on Node via `tsx`; **`--backend elysia`** runs on Bun. `pnpm dev` runs `apps/web` and `apps/api` together. -- **`--orm drizzle` / `--orm prisma`** add the config, a `db/` client, and `.env.example` under `apps/api`. `web` and `api` both depend on `packages/domain` via `workspace:*`. +- **`--backend hono`** runs on Node via `tsx`; **`--backend elysia`** runs on Bun. `pnpm dev` runs `apps/web` (`http://localhost:3000`) and `apps/api` (`http://localhost:8000`) together. Every backend exposes `GET /health-check`; with an ORM selected it also runs a read-only query against the database and returns the rows, so you can confirm the DB wiring end-to-end. +- **`--orm drizzle` / `--orm prisma`** add the config, a `db/` client, `.env.example`, and `db:generate` / `db:migrate` / `db:seed` scripts under `apps/api`. `db:seed` inserts a couple of dummy rows so a fresh DB (and `/health-check`) returns real data. `web` and `api` both depend on `packages/domain` via `workspace:*`. - **`--db postgres` / `--db mysql`** also generate a root `docker-compose.yml` whose credentials match `.env.example`, so `docker compose up -d` gives you a working database. `sqlite` needs nothing extra; `d1` targets Cloudflare Workers (pair with `--deploy cloudflare-workers`). The post-scaffold summary prints the get-started steps for your exact combo (install, dev, and — when a server database is selected — `docker compose up -d` plus the `db:generate` / `db:migrate` commands). +### AI-agent-ready + +Every scaffolded project ships with files that make it work well with Claude Code and other AI agents out of the box: + +- **`AGENTS.md`** (and a `CLAUDE.md` pointing to it) — describes your *exact* stack: real commands, both dev URLs, the monorepo map, the ORM/DB workflow, and the `/health-check` probe. +- **`.claude/settings.json`** — a permission allowlist for routine dev commands (package manager, `docker compose` when a server DB is selected, `wrangler` for Cloudflare, read-only git) so agents don't stall on prompts. +- **`.claude/commands/`** — project slash-commands: `/verify` (typecheck + build + test, and curl the health-check), `/format-fix`, and `/new-component` (follows the repo's naming + JSDoc conventions). +- **`.mcp.json`** — with `--test playwright`, a Playwright MCP server so an agent can drive the running app in a browser. + ## Project Structure ``` diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..bcc875a --- /dev/null +++ b/clean.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh +# Remove the scaffolded my-app folder (dev cleanup). Pass a name to remove a +# different folder: ./clean.sh some-app +target="${1:-my-app}" +if [ -e "$target" ]; then + rm -rf "$target" + echo "Removed $target" +else + echo "Nothing to remove: $target does not exist" +fi diff --git a/cmd/add.go b/cmd/add.go new file mode 100644 index 0000000..e7c1e19 --- /dev/null +++ b/cmd/add.go @@ -0,0 +1,159 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spencer-osbrjp/bungkus-cli/config" + "github.com/spencer-osbrjp/bungkus-cli/pkg" + "github.com/spf13/cobra" +) + +var addCmd = &cobra.Command{ + Use: "add