7.7 KiB
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.
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:
RadicalElements/
├── boboko-core/ (this repo)
└── boboko-test/ (or boboko-starter, boboko-3dealer, ... — 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:
./bin/dc-core.sh exec app <command>
This is shorthand for docker compose -f docker-compose.dev.yml -f docker-compose.core-dev.yml exec app <command>. Use ./bin/dc-core.sh for everything below instead of typing the full compose invocation.
-
Path repository. In the consumer app's
composer.json, therepositoriesarray 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. -
Relaxed version constraint. The consumer app's
composer.jsonshould require"boboko/core": "0.*"(not a tight^0.0.1caret) — otherwise Composer rejects newer0.0.xversions resolved from the path repo. -
Bind mount. The consumer app's
docker-compose.core-dev.ymloverlays../boboko-coreinto the container at/var/www/boboko-core, matching where the path repo resolves it relative to/var/www/html. -
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-runcomposer update boboko/core. Inboboko-test, the entrypoint does this automatically on every dev boot (seedocker/entrypoint.sh), so./bin/dc-core.sh upalone picks up local core changes. If a consumer app's entrypoint doesn't do this yet, run it manually:./bin/dc-core.sh exec app composer update boboko/core --with-all-dependenciesSkipping this step is the most common cause of "my change isn't showing up."
JS/CSS: no separate npm package
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.
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.
Stable entry point. A consumer imports from 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:
// consumer app's resources/js/app.js
import { registerCheckout } from "../../vendor/boboko/core/resources/js/index.js";
registerCheckout(application);
{{-- consumer app's layout --}}
@vite(['vendor/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):
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,
},
},
});
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:
# consumer app's docker-compose.core-dev.yml
services:
vite:
volumes:
- ../boboko-core:/app/vendor/boboko/core
(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.)
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:
./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:
./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, 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/.