181 lines
12 KiB
Markdown
181 lines
12 KiB
Markdown
# Contributing to boboko-core
|
|
|
|
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
|
|
|
|
Consumer apps (`3dealer`, `boboko-test`, …) are checked out next to this repo:
|
|
|
|
```
|
|
RadicalElements/
|
|
├── boboko-core/ (this repo)
|
|
└── 3dealer/ (or boboko-test, ... — consumer app, Docker-based)
|
|
```
|
|
|
|
### 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/core-mode # print the current mode
|
|
bin/core-mode local # work against ../boboko-core
|
|
bin/core-mode repo # back to tagged releases
|
|
```
|
|
|
|
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.
|
|
|
|
(`3dealer` has `bin/core-mode` and `bin/deploy`; they're app-agnostic, so other consumer apps can copy them as-is.)
|
|
|
|
### Day to day in local mode
|
|
|
|
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.
|
|
|
|
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:
|
|
|
|
- **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
|
|
|
|
```bash
|
|
bin/dc-core.sh exec app composer update boboko/core --with-all-dependencies
|
|
```
|
|
|
|
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, 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(['node_modules/@boboko/core/resources/css/checkout.css', 'resources/css/app.css', 'resources/js/app.js'])
|
|
```
|
|
|
|
`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.
|
|
|
|
## Translations added in the UI
|
|
|
|
Default translation lines ship in core's seeders (`StorefrontTranslationsSeeder`, `CheckoutTranslationsSeeder`, `ValidationTranslationsSeeder`), which every app runs on boot and which only ever add missing keys. Lines added while building a storefront usually start in the Filament Language Lines UI instead. To move them into core:
|
|
|
|
```bash
|
|
bin/dc-core.sh exec app php artisan boboko:translations:pull # local mode: writes into ../boboko-core
|
|
bin/dc exec app php artisan boboko:translations:pull --dry-run # any mode: just list them
|
|
```
|
|
|
|
It adds every `storefront` / `checkout` / `validation` key that's in the database but not in the matching seeder, appended at the end of `lines()` under a marker comment — move them into the right section before committing. Keys the seeder already has are never touched, even if their text was edited in the UI. `bin/deploy` runs it for you.
|
|
|
|
## Releasing a version
|
|
|
|
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:
|
|
|
|
```bash
|
|
git tag v0.27.5
|
|
git push origin master v0.27.5
|
|
```
|
|
|
|
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 `php artisan boboko:translations:pull` (see [Translations added in the UI](#translations-added-in-the-ui)). In local mode, if it pulls any lines into `../boboko-core`, it stops — commit, tag and push core, then rerun. In repo mode it only checks, and stops if the database has lines core doesn't.
|
|
3. 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).
|
|
4. 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.
|
|
5. 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
|
|
|
|
There is no automated test suite for this package — too much of Lunar's behavior (table prefixing, nested sets, translatable attributes, Filament panel filters) only breaks in combination, against real Postgres, in a way that's impractical to fake in isolation. Instead, verify changes directly against a consumer app's live database. The practical workflow used throughout this package's `MigrateImport` feature:
|
|
|
|
**One-off checks**, via `php artisan tinker --execute="..."` in the consumer app's container:
|
|
|
|
```bash
|
|
./bin/dc-core.sh exec app php artisan tinker --execute="
|
|
use Modules\Core\MigrateImport\Shopify\Resolvers\ProductTypeResolver;
|
|
\$type = (new ProductTypeResolver())->resolve('Test Type');
|
|
echo \$type->name . PHP_EOL;
|
|
"
|
|
```
|
|
|
|
**Multi-step scripts** (creating related records, checking round-trips), via a raw PHP script bootstrapped like an Artisan command — this avoids tinker's persistent-session quirks and more closely matches how code actually runs in a queued job:
|
|
|
|
```bash
|
|
./bin/dc-core.sh exec app php -r "
|
|
require 'vendor/autoload.php';
|
|
\$app = require 'bootstrap/app.php';
|
|
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
|
|
\$kernel->bootstrap();
|
|
|
|
// ... your verification code ...
|
|
"
|
|
```
|
|
|
|
**Always clean up fixtures** created this way — either via `DB::statement('DELETE FROM ...')` in the same script (respecting foreign key order — Lunar has cascading relations like `lunar_customer_group_product` that block naive deletes), or leave them if they're harmless and the DB is a disposable dev/test instance.
|
|
|
|
Non-obvious Lunar bugs/traps hit while building against it are documented in [docs/lunar.md](docs/lunar.md#gotchas), not here — check there before debugging something that looks like a Lunar quirk.
|
|
|
|
## Code organization
|
|
|
|
Feature work lives under `src/<Feature>/`, with models in a `Models/` subdirectory (see `src/Auth/Models`, `src/Customer/Models`, `src/MigrateImport/Models`). Source-specific importer logic is grouped by source name (e.g. `src/MigrateImport/Shopify/`), with resolver classes (one responsibility each — resolve-or-create a single Lunar entity) grouped further under `Resolvers/`.
|