diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index b441007..6cbb432 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -1,85 +1,136 @@ # Contributing to boboko-core -This is a Composer library, not a runnable app — you can't `php artisan serve` it directly. To develop and verify changes, you need a consumer app wired to a local checkout via a Composer path repository, plus a real database, since a large part of this package (Lunar models, migrations, Filament panel resources) can only be meaningfully verified against a live Lunar install. +This is a Composer library (and an npm package of the same name — see [JS/CSS](#jscss-a-real-npm-package)), not a runnable app — you can't `php artisan serve` it directly. To develop and verify changes, you need a consumer app wired to a local checkout, plus a real database, since a large part of this package (Lunar models, migrations, Filament panel resources) can only be meaningfully verified against a live Lunar install. ## Local dev setup -This works against any consumer app that follows the same convention — `boboko-test`, `boboko-starter`, `boboko-3dealer`, etc. — checked out next to this repo: +Consumer apps (`3dealer`, `boboko-test`, …) are checked out next to this repo: ``` RadicalElements/ ├── boboko-core/ (this repo) -└── boboko-test/ (or boboko-starter, boboko-3dealer, ... — consumer app, Docker-based) +└── 3dealer/ (or boboko-test, ... — consumer app, Docker-based) ``` -Each of these consumer apps ships a `bin/dc-core.sh` helper that wraps the Docker Compose overlay needed to bind-mount a local `boboko-core` checkout into the app container: +### Two modes: local and repo + +A consumer app can consume core in one of two modes, and carries the wiring for both. The inactive one is parked under an underscore-prefixed key: + +| | **local** — your `../boboko-core` checkout | **repo** — tagged releases from the forge | +|---|---|---| +| `composer.json` | `repositories`: path repo `../boboko-core` (`"symlink": true`) | `repositories`: VCS repo `https://code.radical-elements.com/boboko/core.git` | +| `package.json` | `@boboko/core`: `file:../boboko-core` | `@boboko/core`: `git+https://code.radical-elements.com/boboko/core.git#semver:0.x` | +| Docker Compose | `bin/dc-core.sh` (dev + `docker-compose.core-dev.yml` overlay) | `bin/dc` (dev only) | + +- **The committed state is always repo mode.** Local mode rewrites both lockfiles to point at `../boboko-core`, which doesn't exist on the server — never commit it. +- Both sides use an open `0.x` range: `"boboko/core": "0.*"` in Composer, `#semver:0.x` in npm. Don't use a caret: below 1.0.0, `^0.27.0` means `>=0.27.0 <0.28.0` in both tools, so it would silently refuse the next minor. +- `docker-compose.core-dev.yml` bind-mounts `../boboko-core` into the containers — at `/var/www/boboko-core` for `app`/`queue`/`scheduler` (where the path repo resolves from `/var/www/html`) and at `/boboko-core` for `vite` (where `file:../boboko-core` resolves from `/app`). Inside the app container, `vendor/boboko/core` is a symlink into that mount. + +### Switching modes: `bin/core-mode` + +Don't swap the keys by hand — each consumer app ships a `bin/core-mode` script: ```bash -./bin/dc-core.sh exec app +bin/core-mode # print the current mode +bin/core-mode local # work against ../boboko-core +bin/core-mode repo # back to tagged releases ``` -This is shorthand for `docker compose -f docker-compose.dev.yml -f docker-compose.core-dev.yml exec app `. Use `./bin/dc-core.sh` for everything below instead of typing the full compose invocation. +It swaps the `composer.json` / `package.json` wiring, runs `down` with the old mode's Compose wrapper and `up` with the new one, then waits until the entrypoints have re-resolved core. Running it for the mode you're already in skips the edits and just restarts the stack — in repo mode, that's how you pick up a newly pushed tag. -1. **Path repository.** In the consumer app's `composer.json`, the `repositories` array needs a path entry pointing at `../boboko-core`. If it only exists in a disabled block (e.g. `_repositories`), move it into the live array. -2. **Relaxed version constraint.** The consumer app's `composer.json` should require `"boboko/core": "0.*"` (not a tight `^0.0.1` caret) — otherwise Composer rejects newer `0.0.x` versions resolved from the path repo. -3. **Bind mount.** The consumer app's `docker-compose.core-dev.yml` overlays `../boboko-core` into the container at `/var/www/boboko-core`, matching where the path repo resolves it relative to `/var/www/html`. -4. **Re-resolve after every change.** Composer's path repo does not hot-reload — after editing anything in `boboko-core` (including adding new files, which need autoload discovery), the container needs to re-run `composer update boboko/core`. In `boboko-test`, the entrypoint does this automatically on every dev boot (see `docker/entrypoint.sh`), so `./bin/dc-core.sh up` alone picks up local core changes. If a consumer app's entrypoint doesn't do this yet, run it manually: +(`3dealer` has `bin/core-mode` and `bin/deploy`; they're app-agnostic, so other consumer apps can copy them as-is.) - ```bash - ./bin/dc-core.sh exec app composer update boboko/core --with-all-dependencies - ``` +### Day to day in local mode - Skipping this step is the most common cause of "my change isn't showing up." +Use `bin/dc-core.sh` for every Compose command (`bin/dc-core.sh exec app …`, `bin/dc-core.sh logs -f`, …) — plain `docker compose` or `bin/dc` leaves the core mount out. -## JS/CSS: no separate npm package +The dev entrypoints re-resolve core on **every boot**: `docker/entrypoint.sh` runs `composer update "boboko/*"` and `docker/entrypoint-vite.sh` runs `npm update @boboko/core`. So: -This package's JS (Stimulus controllers) and CSS ship as plain source files under `resources/js/` and `resources/css/`, read directly by a consumer app's own Vite build — there is no separate `@boboko/core` npm package, and no `npm install`/`file:` dependency step of any kind. +- **Whatever branch is checked out in `../boboko-core` is what the app runs.** Switching core branches switches the app's code — check which branch you're on before debugging "missing" features. +- PHP edits to existing files show up immediately (it's a symlink). **New classes, new migrations, or `composer.json` changes** need a re-resolve: restart the stack, or run -The reason: Composer already gives every environment one single, unconditional path — `vendor/boboko/core` — whether that resolves to a real symlink into `../boboko-core` (local path repo) or a real installed copy (tagged VCS release). A consumer's `vite.config.js` and JS entry point just read straight from that path, so there is nothing to toggle on the JS side — whatever Composer resolved is exactly what Vite sees, automatically, in both dev and prod. + ```bash + bin/dc-core.sh exec app composer update boboko/core --with-all-dependencies + ``` -**Stable entry point.** A consumer imports from [resources/js/index.js](resources/js/index.js) only — never from a path reaching into a specific module's internals (e.g. `resources/js/checkout/index.js` directly). That barrel file re-exports whatever a consumer needs (currently just `registerCheckout`), so this package's internal file layout can change without breaking every consumer's own entry point: + Skipping this is the most common cause of "my change isn't showing up." +- In `3dealer`, the app container's `vendor/` is a named Docker volume, so the host's `vendor/` directory is stale — inspect packages inside the container, not on the host. + +## JS/CSS: a real npm package + +Core's Stimulus controllers and CSS ship as the `@boboko/core` npm package, installed into the consumer's `node_modules` — as a symlink to `../boboko-core` in local mode, as a real copy of the tagged release in repo mode. It's a real package (rather than files read out of `vendor/`) so npm installs core's own dependencies (`leaflet`, `@hotwired/stimulus`) transitively, the same way Composer does for PHP. + +Public entry points (`package.json` `exports`): + +| Import | File | +|---|---| +| `@boboko/core` | `resources/js/index.js` — the stable barrel (`registerCheckout`, `registerWishlist`, …) | +| `@boboko/core/vite-plugin` | `vite-plugin.js` — `boboko()` | +| `@boboko/core/css/*` | `resources/css/*` | +| `@boboko/core/checkout`, `@boboko/core/checkout/*` | `resources/js/checkout/…` | + +A consumer imports from the `@boboko/core` barrel only — not from a module's internal files — so the internal layout here can change without breaking every consumer: ```js // consumer app's resources/js/app.js -import { registerCheckout } from "../../vendor/boboko/core/resources/js/index.js"; +import { registerCheckout, registerWishlist } from "@boboko/core"; registerCheckout(application); +registerWishlist(application); +``` + +```js +// consumer app's vite.config.js +import { boboko } from "@boboko/core/vite-plugin"; + +export default defineConfig({ + plugins: [ + laravel({ + input: [ + // Core's structural checkout styles load first, so the app's own theming wins. + "node_modules/@boboko/core/resources/css/checkout.css", + "resources/css/app.css", + "resources/js/app.js", + ], + }), + boboko(), + ], +}); ``` ```php {{-- consumer app's layout --}} -@vite(['vendor/boboko/core/resources/css/checkout.css', 'resources/css/app.css', 'resources/js/app.js']) +@vite(['node_modules/@boboko/core/resources/css/checkout.css', 'resources/css/app.css', 'resources/js/app.js']) ``` -**What a consumer's `vite.config.js` needs**, because `vendor/boboko/core` is a symlink in local path-repo dev (not a real directory): +`boboko()` owns the Vite settings the local-mode symlink needs, so consumers don't hand-copy them: it excludes `@boboko/core` from dependency pre-bundling (otherwise Vite serves a stale cached copy after you edit core), pre-bundles `leaflet`/`@hotwired/stimulus` explicitly, and turns on `resolve.preserveSymlinks` and `server.watch.followSymlinks` so bare imports resolve from the consumer's `node_modules` and core edits trigger HMR. All of it is a harmless no-op against a real installed copy in repo mode. -```js -export default defineConfig({ - server: { - watch: { - // vendor/boboko/core is a symlink into ../boboko-core in local - // path-repo dev. Vite/chokidar don't follow symlinks for watched - // files by default, so edits to core's source wouldn't otherwise - // trigger HMR. No-op against a real installed copy (tagged VCS - // release) in production — there's no symlink to follow, and - // production only ever runs a one-shot `npm run build`, which - // doesn't watch anything regardless. - followSymlinks: true, - }, - }, -}); -``` +## Releasing a version -Bare imports inside this package's own JS (`leaflet`, `@hotwired/stimulus`) resolve against the *consumer's* `node_modules` — Node's normal upward `node_modules` resolution walks from `vendor/boboko/core/resources/js/...` up through `vendor/boboko/`, `vendor/`, to the consumer app's root, where `node_modules` lives. This works with zero extra config as long as `vendor/boboko/core` sits inside the consumer's own directory tree (true for both the symlink and the real-copy case) — a consuming app's `vite`-equivalent Docker service just needs the same bind mount PHP containers already get, landing at the same path: +1. Bump `"version"` in **both** `composer.json` and `package.json` — they must match. +2. Add a `CHANGELOG.md` entry under the new version. While pre-1.0, a new capability for consuming apps is a **minor** bump (`0.27.x` → `0.28.0`); a fix, redesign or internal swap with no new capability is a **patch** bump. +3. Commit, tag `vX.Y.Z`, and push the commit **and** the tag: -```yaml -# consumer app's docker-compose.core-dev.yml -services: - vite: - volumes: - - ../boboko-core:/app/vendor/boboko/core -``` + ```bash + git tag v0.27.5 + git push origin master v0.27.5 + ``` -(Match whatever the consumer's Vite container's working directory actually is — `/app` above, `/var/www/html` for the PHP containers in `boboko-test`'s convention.) +A tag alone changes nothing in production — each consumer app has to pick it up and deploy (below). + +## Deploying a consumer app + +Production only ever runs what the app's **committed lockfiles** pin. Each consumer app ships `bin/deploy`, which: + +1. Refuses to run on the wrong branch, with uncommitted changes (other than the core wiring files), or behind `origin`. +2. Runs `bin/core-mode repo` — switching from local mode if needed, restarting either way — so both lockfiles resolve the newest `0.x` tag. It warns if `../boboko-core` has a newer tag than what resolved (usually an unpushed tag). +3. Commits the lockfile bump (`Chore: Bumping boboko/core to X.Y.Z`) if there is one, shows what will be pushed, and asks for confirmation. +4. Pushes, then runs `vendor/bin/envoy run deploy` against the host in `.env.envoy`. + +Envoy (`Envoy.blade.php`) then, on the server: `git reset --hard` + `git pull`, `docker compose build` (the `production` image target runs `composer install --no-dev` and `npm ci` from the committed lockfiles — this is where the core tag actually lands), `up -d`, caches config/routes/events, restarts `queue` and `scheduler`, and regenerates Stoic thumbnails. The production entrypoint skips Composer entirely and runs migrations (including core's), seeders, the Meilisearch sync, and `artisan optimize`. + +After deploying you're left in repo mode — `bin/core-mode local` to go back. + +When a change spans core and the app (e.g. a core migration plus an app model cast that depends on it), ship them together: tag core first, then commit the app change and deploy — `bin/deploy` bumps the lock to the new tag in the same deploy. ## Verifying changes against a real database diff --git a/resources/js/index.js b/resources/js/index.js index 154fb46..74b4918 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -1,5 +1,5 @@ // Single stable JS entry point for this package. A consuming app imports -// from here (vendor/boboko/core/resources/js/index.js), never from a path +// from here (`import { … } from "@boboko/core"`), never from a path // reaching into a specific module's internals — so this file's exports can // grow or its modules' internal layout can change without breaking every // consumer's own entry point.