# Releasing the DealFlow plugin

Single source of truth: the canonical plugin tree at `plugins/DealFlowBundle/`.
Build releases **only** with `make package` from that directory — never hand-zip,
never zip a Docker copy.

## Steps
1. Bump the version in `Config/config.php` (the `'version' => '…'` line — the
   Makefile reads it) **and** `composer.json`. Add a `CHANGELOG.md` entry.
2. `make package` → `DealFlowBundle-<version>.zip`. The target self-verifies that
   all critical Deal sub-controllers are present, and excludes dev cruft + nested
   `*.zip`s. If a controller is missing it **aborts** (the guardrail 0.9.1 lacked).
3. Verify the artifact in a real Mautic install before shipping — see the harness
   at `../../FirmOptic-Mautic/docker/mautic7/`. Fresh-unzip the new zip,
   `cache:clear` + `mautic:plugins:reload`, then exercise the deal routes,
   **including the ajaxmodal "New Deal" → Save & Close flow** (not just page
   loads). Run the e2e suite at `FirmOptic-Mautic/e2e` (BASE_URL=…, 110 tests).
4. Ship: copy the zip to `FirmOptic-Mautic/site/releases/`, point
   `DealFlowBundle-latest.zip` at it, then `gcloud run deploy deal-flow-site
   --source .` from `site/`. Verify the live download at
   `https://dealflowplugin.com/releases/DealFlowBundle-<version>.zip`.
5. The in-plugin auto-updater is gated by the Firestore `releases` collection
   (GCP project `harborwind-researcher`). Add a doc there **only** when you want
   licensed instances offered the update — it is a deliberate manual gate.
   The doc id is the version string; **all fields below are required** (the
   `/api/updates/check` endpoint orders by `released_at` and the plugin's
   Updates page displays it — an absent/stale `released_at` is exactly the
   wrong-date bug a customer reported in June 2026, and skipping the gate
   entirely left the line stuck at 0.8.8 while 0.9.x shipped):

   | Field | Type | Example |
   |---|---|---|
   | `version` | string | `"0.9.4"` |
   | `released_at` | string (ISO-8601 UTC) | `"2026-06-10T17:00:00.000Z"` — the actual publish date, **set it the day you ship** |
   | `download_url` | string | `"https://dealflowplugin.com/releases/DealFlowBundle-0.9.4.zip"` |
   | `sha256` | string | `shasum -a 256 DealFlowBundle-<version>.zip` |
   | `changelog` | string | 1–3 customer-facing lines from CHANGELOG.md |
   | `requires_migration` | bool | `true` if the release adds a `Migrations/Version_*` |

   Write it with the REST API (no console needed):
   ```bash
   TOKEN=$(gcloud auth print-access-token)
   curl -s -X PATCH \
     "https://firestore.googleapis.com/v1/projects/harborwind-researcher/databases/(default)/documents/releases/<version>" \
     -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
     -d '{"fields":{"version":{"stringValue":"<version>"},"released_at":{"stringValue":"<iso-date>"},"download_url":{"stringValue":"<url>"},"sha256":{"stringValue":"<sha>"},"changelog":{"stringValue":"<text>"},"requires_migration":{"booleanValue":false}}}'
   ```
   Then verify the endpoint advertises it: `curl -s <license-api>/api/updates/check?...`
   should return the new `latest_version` **and** `released_at`.

## Two failure modes that have bitten us — do not repeat
- **0.9.1** — hand-zipped from a stale Docker copy → omitted the 6 `DealController`
  sub-controllers plus their `Trait/`/`Helper/`/`Compat/`/`ViewModel/` deps → HTTP
  500 on every deal route on a fresh install (`Controller "…DealListController"
  does neither exist as service nor as class`). Always `make package`; the
  self-verify guardrail now catches this.
- **0.9.2** — a controller-split regression: the modal-save `contentTemplate`
  pointed at the forwarding `DealController` **facade**, whose `forward()`
  sub-request re-wrapped the response as JSON-in-JSON → the "New Deal" modal Save
  dumped raw JSON into the page. Fixed in 0.9.3 by pointing modal
  `contentTemplate`s at the real rendering controllers (`DealList`/`DealForm`/
  `DealView`). **Never use the `DealController` facade as a `contentTemplate`.**
