Files
core/CONTRIBUTE.md
T

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.

  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:

    ./bin/dc-core.sh exec app composer update boboko/core --with-all-dependencies
    

    Skipping 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/.