Working on the code
The commands you run every day, the tests, the checks before a push and where things live.
This guide is the developer's daily reference: how to start the application, which Artisan commands the kit adds, how the two test suites run, what stops a push, and which folders you will actually touch. Read it once after /help/get-it-running and come back when a command's name escapes you.
Start everything
One command starts the four processes a working copy needs:
composer dev
It is a Composer script that runs npx concurrently with, in this order and each in its own colour: php artisan serve (server), php artisan queue:listen --tries=1 (queue), php artisan pail --timeout=0 (logs) and npm run dev (vite). Stopping one stops all four (--kill-others).
If you serve the site with Herd or Valet, the PHP server is redundant: open the APP_URL from your .env (for example http://your-project.test) and run the other three yourself in separate terminals.
npm run dev # Vite with hot reload
php artisan queue:listen --tries=1
php artisan pail
The queue matters even in development: notifications, web push and billing work are jobs, and nothing happens until a worker takes them. When you would rather not run one, set QUEUE_CONNECTION=sync in .env and jobs run inline in the request. Both are explained in /help/queues-and-scheduled-work.
The rest of the day-to-day set is plain Laravel:
| Command | When |
|---|---|
php artisan migrate |
After pulling a migration. |
php artisan db:seed |
Once, after the first migration, to create the seeded accounts, roles and permissions. --class=UserSeeder or --class=AdminSeeder reseeds one group; the seeders use firstOrCreate, so an existing account keeps its password. |
php artisan tinker |
To inspect or fix a record from the console with the application's models. |
npm run build |
To compile the assets once, without watching. |
php artisan schedule:work |
To run the scheduled commands locally, every minute, while it stays open. |
The seeded accounts come from config/app.php: the emails in sudo become administrators, the ones in default_users become regular users. Each password is the part of the email before @, reversed and lowercased, so [email protected] signs in with tset. A seeded administrator is asked to change that password on first sign-in.
The Artisan commands the kit adds
These are the kit's own commands, in app/Console/Commands:
| Command | What it does |
|---|---|
help:stamp |
Rewrites the fingerprint comment on every translated help document so it records which English version it was written from. Reads and hashes files only; never calls a network. |
help:stamp --check |
Writes nothing. Prints a table of help documents that are missing a translation, whose English changed after the translation was written, whose English no longer exists, or that have no front matter, and exits with a failure code when the table is not empty. |
help:stamp --check --pushed |
The same report, limited to the help documents a push changes, as they stand in the commits it sends. The pre-push hook runs it with the refs Git passes on standard input. |
lang:search |
Scans app/, resources/views/ and routes/ for __(), @lang(), trans(), Lang::get(), Laravel Front input labels, ->setTitle() and action titles, and adds every key that is missing from lang/en.json. |
lang:sync |
Makes every locale file carry the same keys as English, in the same order. A key that exists only in another locale is added to English; a key missing from a locale is translated through the configured AI provider, or left in English when none is configured. Covers lang/*.json and lang/{locale}/*.php. |
lang:delete |
Removes from every lang/*.json file the keys that no file of the project uses any more. Never touches the PHP translation files. |
lang:update |
Runs lang:sync, then lang:search, then lang:sync again: the whole translation round in one command. |
pwa:generate-assets |
Asks for a square base icon of at least 512 px and two background colours, then writes every icon and splash screen size that config/laravelpwa.php lists into public/images/icons. |
stats:compute-daily |
Counts the users who signed in yesterday and stores the number in the stats table under daily_logged_users. Scheduled in routes/console.php to run daily at 00:00. |
The weblabormx/billing-core package in packages/ adds one more:
| Command | What it does |
|---|---|
billing:sync-exchange-rates {--date=} |
Fetches and stores the exchange rates for the paid subscription dates that need a currency conversion. Scheduled daily at 12:00, and only while plans or add-ons are enabled. |
Installed packages add the commands you will also reach for: clean:code from the Weblabor coding standards package (--no-commit skips the commit it makes), webpush:vapid from the web push channel, dusk and dusk:chrome-driver from Laravel Dusk, ide-helper:generate and ide-helper:models from the IDE helper (its output files are ignored by Git), and make:agent, make:tool, make:agent-middleware and agent:chat from the Laravel AI package, described in /help/connect-an-ai-provider. php artisan list prints them all.
The tests
There are two suites and they run differently.
Feature tests
phpunit.xml declares one suite, WeblaborBase, over tests/WeblaborBase. Its folders are Admin, Auth, BillingCore, Categories and Help, one per area of the kit. The suite runs on an in-memory SQLite database, so it needs no database server and leaves your development data alone:
Setting forced by phpunit.xml |
Value |
|---|---|
APP_ENV |
testing |
DB_CONNECTION / DB_DATABASE |
sqlite / :memory: |
QUEUE_CONNECTION |
sync |
MAIL_MAILER |
array |
CACHE_DRIVER, SESSION_DRIVER |
array |
tests/TestCase.php goes further before the application boots: it deletes bootstrap/cache/config.php so a cached MySQL configuration cannot leak into the run, loads .env and then .env.testing on top of it, and forces SQLite once more. .env.testing is committed and carries an APP_KEY plus the SQLite settings, which is why a fresh clone can run the tests before it has a .env.
Extend Tests\FeatureTestCase for a test that touches the database. It adds RefreshDatabase and seeds PermissionSeeder and RoleSeeder before each test, so config('app.admin_role') exists and a user can be given it. Tests\TestCase is the bare base for a test that needs neither.
php artisan test # the whole suite
php artisan test tests/WeblaborBase/Admin/DevZoneTest.php # one file
php artisan test --filter=test_user_can_login # one method
composer test # config:clear, then the suite
composer test-without-billing-core runs scripts/ci/test-without-billing-core.sh: it copies the project to a temporary folder, removes the weblabormx/billing-core package there, migrates and seeds a throwaway SQLite file, checks that no billing route survives, and runs two feature tests. It proves that the kit still boots when a project ships without billing. Your working copy is not modified.
Browser tests
tests/Browser/WeblaborBase holds the Laravel Dusk tests, in Auth and App folders. They drive a real Chrome through ChromeDriver, against the application as your .env serves it, so the site must be reachable at APP_URL and the database you configured is the one they use. They are not part of php artisan test.
php artisan dusk:chrome-driver --detect # once, and after Chrome updates
php artisan dusk # every browser test
php artisan dusk tests/Browser/WeblaborBase/Auth/LoginTest.php
tests/DuskTestCase.php starts ChromeDriver on port 9515 unless you run inside Sail, and opens Chrome headless at 1920×1080. Three environment variables change that, read from the shell or from .env:
| Variable | Effect |
|---|---|
DUSK_DRIVER_URL |
Where ChromeDriver listens. Default http://localhost:9515. |
DUSK_HEADLESS_DISABLED |
Set to anything to watch the browser. php artisan dusk --browse sets it for you. |
DUSK_START_MAXIMIZED |
Set to anything to maximize the window instead of the fixed size. |
When a file named .env.dusk.local exists (.env.dusk.{APP_ENV}), Dusk swaps it in for .env while the tests run and restores .env afterwards. Use it to point the browser tests at a separate database.
Extend Tests\Browser\WeblaborBase\WeblaborBaseDuskTestCase. It gives you makeVerifiedUser() (a verified account whose password is password), makeAdminUser() (the same, with the admin role after seeding permissions and roles), ensureActiveSubscription() for a flow behind the paywall, fillWireModel() to type into a wire:model field, clickFirstSubmitButton(), assertBrowserPageLoaded() and latestVerificationCodeFromLog(), which reads the last verification code written to the log and therefore needs MAIL_MAILER=log. Every account created through these helpers is force-deleted in tearDown(). Failed tests leave a screenshot in tests/Browser/screenshots, the console output in tests/Browser/console and the page source in tests/Browser/source; the three folders are ignored by Git.
Playwright is present in package.json as a development dependency, but no test in the repository uses it; the browser suite runs on Dusk and ChromeDriver.
Static analysis and style
vendor/bin/phpstan analyze
php artisan clean:code --no-commit
Larastan is installed for the first. There is no phpstan.neon committed, so pass the paths and level you want on the command line or add the file to your project. The second applies the Weblabor coding standards to the whole project; without --no-commit it commits the result. That is also the one job the committed GitLab pipeline runs: .gitlab-ci.yml installs the dependencies with scripts/ci/prepare-clean-code.sh, runs clean:code --no-commit, and pushes a "Code Style Fixes" commit when anything changed. Its other jobs merge master into staging and staging into develop after each push to those branches. The pipeline does not run the tests.
composer install-locally-repo is for someone developing the Weblabor packages themselves: it replaces vendor/weblabormx/weblabor-cs, laravel-front and tall-utils with symlinks to sibling checkouts. Leave it alone unless you are.
Debugging while you work
In the local environment with APP_DEBUG=true the Debugbar shows the queries, views, events and timing of every page, and the query detector reports N+1 queries inside it. DEBUGBAR_ENABLED, QUERY_DETECTOR_ENABLED, QUERY_DETECTOR_THRESHOLD and the other variables that tune them, the log channels including Slack, and the exception throttle are all in /help/logs-and-debugging. php artisan pail tails the log in the terminal and /logs shows it in the browser.
To receive Stripe webhooks on your machine, forward them with the Stripe CLI to the route the kit exposes in routes/api.php, then put the secret it prints in .env:
stripe listen --forward-to your-project.test/api/stripe/webhook
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
What stops a push
composer install runs git config core.hooksPath scripts/git-hooks at the end of every autoload dump, so the hooks in that folder are active in every clone, including the projects you derive from the kit. There is one, pre-push, and it runs:
php artisan help:stamp --check --pushed
It judges what the push sends, not what is in your working folder. Only the help documents the push adds, changes, moves or deletes are checked, together with their pair (the translation of an English document, the English of a translation), as they stand in the commits being pushed. A push is refused when one of them lacks a translation, has a translation older than its English, has a translation whose English is gone, or has no front matter. The table it prints names each file and the problem.
- Work that is not in a commit does not count: uncommitted edits and untracked files neither stop a push nor rescue it.
- A document already broken on the branch does not stop a push that does not touch it.
- A push that touches no help document checks nothing. A new branch is compared with what the remote does not have yet, several branches pushed together are checked together, and deleting a branch or pushing a tag checks nothing.
php artisan help:stamp --check on its own still checks every document in the working folder, which is the check to run while you write. Fix the documents, run php artisan help:stamp to restamp the translations, and push again. For the one time you need to push regardless:
git push --no-verify
The hook needs PHP and a bootable application in the shell that pushes; from a Git client that cannot run php artisan, it fails, and --no-verify is the workaround.
Where things live
| Folder | What you put there |
|---|---|
app/Livewire/Admin, App, Auth, Web, Shared |
Every interactive screen. Admin is the admin panel, App the signed-in area, Auth sign-in, profile and account, Web the public pages, Shared components used by more than one area. |
app/Front/Resources |
The admin panel CRUDs, one class per model, with its policy in app/Policies. app/Front/Inputs, Filters, Actions and Pages hold their custom pieces. |
app/Models |
Eloquent models, all extending App\Models\Model, with $guarded and never $fillable. Casts, enums, traits and observers are in /help/models-casts-and-building-blocks. |
app/Helpers |
Global functions, one file per layer (base.php for the kit); every function is guarded with function_exists so a derived project can add its own file. |
app/Console/Commands |
Artisan commands. Schedules go in routes/console.php. |
app/Services, app/Jobs, app/Notifications, app/Mail |
What a model method delegates to. |
routes/web.php, auth.php, app.php, admin.php, api.php |
Public, authentication, /app, /admin and API routes. Which middleware wraps each group is in /help/routes-layouts-and-middleware. |
resources/views/components |
Blade components: <x-date-input>, <x-phone-input>, <x-email-input>, <x-domain-input>, <x-categories-input>, <x-language-switcher> and the audio player; the design components (<x-design::sidebar-menu>, <x-design::records-table>, <x-design::loading-overlay>, <x-design::system-warnings> and the rest) live in weblabor-base/. Look here before writing markup twice. |
resources/views/livewire, layouts, landing, pages, emails |
Component views, page layouts, the landing, static pages and mail templates. |
lang/en.json, lang/es.json, lang/{locale}/*.php |
Every string a person reads, written in English in code and translated here. |
docs/{project}/help/guides, docs/{project}/help/changelog |
The help centre you are reading, with translations in a locale folder beside each English file. |
stubs/ |
The templates php artisan make:* uses, adjusted to the kit's conventions. |
packages/ |
Packages that ship inside the repository, today billing-core. |