# Deploying

Production runs on **Plesk**, serving from the `main` branch. Deploying is
pulling that branch on the server and running the steps below; there is no
CI/CD pipeline, so nothing happens automatically when you merge.

## Steps

```bash
git pull

composer install --no-dev --optimize-autoloader
npm ci && npm run build          # required: see "Assets" below

php artisan migrate

php artisan config:clear
php artisan route:clear
php artisan view:clear
```

## PHP version

Set in Plesk under **Websites & Domains → PHP Settings**, and it must satisfy
the constraint in `composer.json`. `config.platform.php` there is pinned to
the production version deliberately: it makes local dependency resolution
target the same runtime, so a package that needs a newer PHP cannot end up in
`composer.lock` and break `composer install` on the server. That has happened
before.

If `composer install` refuses to run, compare the Plesk PHP version against
`composer.json` first.

## Assets

`npm run build` is not optional. Laravel resolves asset URLs through
`public/build/manifest.json`, and if that file is missing every page using
`@vite` throws `ViteManifestNotFoundException` — a 500, not a degraded page.

`php artisan deploy:check-assets` verifies the manifest exists and parses, and
exits non-zero if not. Worth running before directing traffic at a new deploy.

## Verifying

```bash
php artisan about --only=environment   # confirms env, debug mode, URL
php artisan route:list | head          # confirms routes resolve
```

Check `APP_URL` matches the real domain. MCP OAuth discovery endpoints are
built from `config('app.url')`, so a wrong value there hands clients
unreachable addresses.

## Rolling back

There is no automated rollback. `git checkout` the previous commit and re-run
the steps above. Migrations are not reversed automatically — check whether the
deploy included any before assuming a checkout is sufficient.
