|
@@ -36,8 +36,9 @@ per-cell audit trail.
|
|
|
`simplexml`, `mbstring`, `fileinfo` ship with the base image.
|
|
`simplexml`, `mbstring`, `fileinfo` ship with the base image.
|
|
|
4. **`tests`** (`FROM runtime`) — re-runs `composer install` to layer
|
|
4. **`tests`** (`FROM runtime`) — re-runs `composer install` to layer
|
|
|
in dev deps (phpunit, etc.) and ships a `composer test` CMD. Built
|
|
in dev deps (phpunit, etc.) and ships a `composer test` CMD. Built
|
|
|
- on demand by `make test` / `make check` via the dev compose's
|
|
|
|
|
- `tests` profile, never started by `make dev` or `make prod`.
|
|
|
|
|
|
|
+ on demand by `appctl test` / `appctl check` via the dev compose's
|
|
|
|
|
+ `tests` profile, never started by `appctl dev start` or
|
|
|
|
|
+ `appctl prod start`.
|
|
|
- Language: PHP 8.3, strict types, PSR-12.
|
|
- Language: PHP 8.3, strict types, PSR-12.
|
|
|
- Database: SQLite via PDO, file at `/var/www/data/app.sqlite` (mounted volume).
|
|
- Database: SQLite via PDO, file at `/var/www/data/app.sqlite` (mounted volume).
|
|
|
- Front end (Phase 19):
|
|
- Front end (Phase 19):
|
|
@@ -74,7 +75,9 @@ per-cell audit trail.
|
|
|
├── docker-compose.yml # prod stack — builds target=runtime
|
|
├── docker-compose.yml # prod stack — builds target=runtime
|
|
|
├── docker-compose.dev.yml # dev overlay — adds bind mounts, css-watcher
|
|
├── docker-compose.dev.yml # dev overlay — adds bind mounts, css-watcher
|
|
|
│ # sidecar, tests profile (see §11)
|
|
│ # sidecar, tests profile (see §11)
|
|
|
-├── Makefile # dev/prod/check wrappers; HOST_UID/GID export
|
|
|
|
|
|
|
+├── appctl # → bin/appctl symlink (dev entry point)
|
|
|
|
|
+├── bin/appctl # dev/prod/check wrappers; HOST_UID/GID export
|
|
|
|
|
+├── bin/appctl-completion.bash # bash completion for appctl (auto-offered on first run)
|
|
|
├── .dockerignore
|
|
├── .dockerignore
|
|
|
├── .env.example
|
|
├── .env.example
|
|
|
├── composer.json / composer.lock
|
|
├── composer.json / composer.lock
|
|
@@ -1676,18 +1679,24 @@ Nothing scheduled.
|
|
|
|
|
|
|
|
## 11. Running locally
|
|
## 11. Running locally
|
|
|
|
|
|
|
|
-The project ships **two compose configurations** plus a Makefile that
|
|
|
|
|
-wraps the long `docker compose -f … -f …` invocations. Pick the one
|
|
|
|
|
-that matches what you're doing:
|
|
|
|
|
|
|
+The project ships **two compose configurations** plus the `appctl`
|
|
|
|
|
+shell wrapper that hides the long `docker compose -f … -f …`
|
|
|
|
|
+invocations. Pick the one that matches what you're doing:
|
|
|
|
|
|
|
|
| Goal | Command | What runs |
|
|
| Goal | Command | What runs |
|
|
|
|---|---|---|
|
|
|---|---|---|
|
|
|
-| Run prod build (or operate it) | `make prod` | `docker-compose.yml` only — the `runtime` image with baked CSS |
|
|
|
|
|
-| Iterate on code locally | `make dev` | `docker-compose.yml` + `docker-compose.dev.yml` — adds source bind mounts, `APP_ENV=development`, and a `css-watcher` sidecar that runs `tailwindcss --watch` against the host |
|
|
|
|
|
-| Lint + tests, one-shot | `make check` | Builds the `tests` Dockerfile target on demand and runs `php -l` + PHPUnit in a `--rm` container; doesn't require `make dev` to be running |
|
|
|
|
|
|
|
+| Run prod build (or operate it) | `./appctl prod start` | `docker-compose.yml` only — the `runtime` image with baked CSS |
|
|
|
|
|
+| Iterate on code locally | `./appctl dev start` | `docker-compose.yml` + `docker-compose.dev.yml` — adds source bind mounts, `APP_ENV=development`, and a `css-watcher` sidecar that runs `tailwindcss --watch` against the host |
|
|
|
|
|
+| Lint + tests, one-shot | `./appctl check` | Builds the `tests` Dockerfile target on demand and runs `php -l` + PHPUnit in a `--rm` container; doesn't require the dev stack to be running |
|
|
|
|
|
|
|
|
-Without `make` installed (`sudo apt-get install -y make` on Debian),
|
|
|
|
|
-the same commands written out:
|
|
|
|
|
|
|
+`appctl` has no dependencies beyond bash + `docker compose`; the
|
|
|
|
|
+script lives at `bin/appctl` with a top-level `./appctl` symlink. The
|
|
|
|
|
+first interactive invocation offers to add a `source` line for
|
|
|
|
|
+`bin/appctl-completion.bash` to your `~/.bashrc` so tab-completion on
|
|
|
|
|
+`appctl dev <TAB>` etc. lights up; answer `n` to skip permanently
|
|
|
|
|
+(re-asking is suppressed via `~/.config/appctl/completion-installed`).
|
|
|
|
|
+
|
|
|
|
|
+Without `appctl`, the same commands written out by hand:
|
|
|
|
|
|
|
|
```bash
|
|
```bash
|
|
|
# Dev (foreground; sidecar regenerates CSS on save)
|
|
# Dev (foreground; sidecar regenerates CSS on save)
|
|
@@ -1702,7 +1711,7 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml \
|
|
|
--profile test run --rm tests
|
|
--profile test run --rm tests
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-`make help` lists all targets; the rest of this section is the *why*.
|
|
|
|
|
|
|
+`./appctl help` lists every command; the rest of this section is the *why*.
|
|
|
|
|
|
|
|
### 11.1 First-time setup
|
|
### 11.1 First-time setup
|
|
|
|
|
|
|
@@ -1710,7 +1719,7 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml \
|
|
|
cp .env.example .env
|
|
cp .env.example .env
|
|
|
# Fill Entra vars, OR set LOCAL_ADMIN_EMAIL + LOCAL_ADMIN_PASSWORD_HASH
|
|
# Fill Entra vars, OR set LOCAL_ADMIN_EMAIL + LOCAL_ADMIN_PASSWORD_HASH
|
|
|
# (see README's Quick setup for the password_hash() one-liner)
|
|
# (see README's Quick setup for the password_hash() one-liner)
|
|
|
-make prod # or `docker compose up --build`
|
|
|
|
|
|
|
+./appctl prod start # or `docker compose up --build`
|
|
|
# open http://localhost:8080
|
|
# open http://localhost:8080
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -1721,7 +1730,7 @@ the request path only checks and 503s if pending, never auto-migrates).
|
|
|
|
|
|
|
|
### 11.2 Dev iteration loop
|
|
### 11.2 Dev iteration loop
|
|
|
|
|
|
|
|
-`make dev` (or the long form above) starts two services:
|
|
|
|
|
|
|
+`./appctl dev start` (or the long form above) starts two services:
|
|
|
|
|
|
|
|
- **`app`** — `runtime` image with `src/`, `views/`, `public/`,
|
|
- **`app`** — `runtime` image with `src/`, `views/`, `public/`,
|
|
|
`assets/`, `migrations/`, `bin/`, `tailwind.config.js`,
|
|
`assets/`, `migrations/`, `bin/`, `tailwind.config.js`,
|
|
@@ -1735,8 +1744,8 @@ the request path only checks and 503s if pending, never auto-migrates).
|
|
|
`node_modules` once, then `tailwindcss --watch` regenerates
|
|
`node_modules` once, then `tailwindcss --watch` regenerates
|
|
|
`public/assets/css/app.css` on every save under `views/`, `src/`, or
|
|
`public/assets/css/app.css` on every save under `views/`, `src/`, or
|
|
|
`public/assets/js/`. Runs as `${HOST_UID}:${HOST_GID}` (exported by
|
|
`public/assets/js/`. Runs as `${HOST_UID}:${HOST_GID}` (exported by
|
|
|
- the Makefile) so files written into the bind-mounted host paths
|
|
|
|
|
- land with normal ownership and don't need a `sudo chown` later.
|
|
|
|
|
|
|
+ `appctl`) so files written into the bind-mounted host paths land
|
|
|
|
|
+ with normal ownership and don't need a `sudo chown` later.
|
|
|
|
|
|
|
|
Edit cycle:
|
|
Edit cycle:
|
|
|
|
|
|
|
@@ -1744,17 +1753,17 @@ Edit cycle:
|
|
|
|---|---|
|
|
|---|---|
|
|
|
| `.twig` template, `.php` source | next browser request (no rebuild) |
|
|
| `.twig` template, `.php` source | next browser request (no rebuild) |
|
|
|
| `.css` / new Tailwind class in a view or `.js` | watcher regenerates `app.css` in <1s; F5 |
|
|
| `.css` / new Tailwind class in a view or `.js` | watcher regenerates `app.css` in <1s; F5 |
|
|
|
-| `composer.json/.lock` or `package.json/.lock` | `make dev-build` |
|
|
|
|
|
-| `Dockerfile` | `make dev-build` |
|
|
|
|
|
|
|
+| `composer.json/.lock` or `package.json/.lock` | `./appctl dev build` |
|
|
|
|
|
+| `Dockerfile` | `./appctl dev build` |
|
|
|
|
|
|
|
|
### 11.3 Tests + lint
|
|
### 11.3 Tests + lint
|
|
|
|
|
|
|
|
The preferred path is one of:
|
|
The preferred path is one of:
|
|
|
|
|
|
|
|
```bash
|
|
```bash
|
|
|
-make check # lint + phpunit, in a one-shot tests container
|
|
|
|
|
-make test # phpunit only
|
|
|
|
|
-make lint # php -l on src/ + tests/
|
|
|
|
|
|
|
+./appctl check # lint + phpunit, in a one-shot tests container
|
|
|
|
|
+./appctl test # phpunit only
|
|
|
|
|
+./appctl lint # php -l on src/ + tests/
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
All three use the dev compose's `tests` service (profile `test`), which
|
|
All three use the dev compose's `tests` service (profile `test`), which
|
|
@@ -1764,7 +1773,7 @@ subsequent runs reuse it.
|
|
|
|
|
|
|
|
The Phase 20 parser tests need `ext-dom`, `ext-zip`, `ext-xmlreader`,
|
|
The Phase 20 parser tests need `ext-dom`, `ext-zip`, `ext-xmlreader`,
|
|
|
`ext-simplexml`, and `ext-gd` — all present in the runtime image, so
|
|
`ext-simplexml`, and `ext-gd` — all present in the runtime image, so
|
|
|
-running them via `make test` is always green. On hosts where you'd run
|
|
|
|
|
|
|
+running them via `./appctl test` is always green. On hosts where you'd run
|
|
|
PHPUnit directly without Docker, those parser tests auto-skip via
|
|
PHPUnit directly without Docker, those parser tests auto-skip via
|
|
|
`extension_loaded()` in `setUp()`.
|
|
`extension_loaded()` in `setUp()`.
|
|
|
|
|
|
|
@@ -1775,14 +1784,14 @@ during writing:
|
|
|
for f in $(git ls-files '*.php'); do php -l "$f" | tail -1 | sed "s|^|$f: |"; done
|
|
for f in $(git ls-files '*.php'); do php -l "$f" | tail -1 | sed "s|^|$f: |"; done
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-…but `make lint` does the same thing inside the container with the
|
|
|
|
|
|
|
+…but `./appctl lint` does the same thing inside the container with the
|
|
|
known-good extension set.
|
|
known-good extension set.
|
|
|
|
|
|
|
|
### 11.4 Running checks via the `/check` Claude Code skill
|
|
### 11.4 Running checks via the `/check` Claude Code skill
|
|
|
|
|
|
|
|
A project-level skill at `.claude/skills/check/SKILL.md` invokes a
|
|
A project-level skill at `.claude/skills/check/SKILL.md` invokes a
|
|
|
Haiku-powered subagent (`.claude/agents/container-tester.md`) that runs
|
|
Haiku-powered subagent (`.claude/agents/container-tester.md`) that runs
|
|
|
-`make check` and reports back **only** failures + a one-line summary.
|
|
|
|
|
|
|
+`./appctl check` and reports back **only** failures + a one-line summary.
|
|
|
Verbose phpunit / docker build output is consumed by the agent and
|
|
Verbose phpunit / docker build output is consumed by the agent and
|
|
|
never enters the main session context.
|
|
never enters the main session context.
|
|
|
|
|
|