Contributing
Contributing
Contributions to externa-core should match the existing Laravel + Inertia React stack, keep enums as the source of truth for permissions/field types/roles, and leave tests green.
Work in the externa-core directory unless you are editing these docs (externa-docs).
Tooling
PHP — Pint
Laravel Pint formats PHP. Composer scripts:
composer lint # pint --parallel
composer lint:check # pint --parallel --test
composer test runs lint:check before Pest.
Frontend — ESLint & Prettier
From externa-core:
npm run lint # eslint . --fix
npm run lint:check # eslint .
npm run format # prettier --write resources/
npm run format:check # prettier --check resources/
npm run types:check # tsc --noEmit
CI-style gate:
composer ci:check
(runs npm lint/format/types checks + composer test).
Wayfinder
Laravel Wayfinder + @laravel/vite-plugin-wayfinder generates typed route/action helpers under resources/js during Vite build/dev.
- Prefer Wayfinder helpers over hard-coded URL strings in React.
- After adding/renaming named routes, run Vite (
npm run devorbuild) so generated clients refresh. - Do not hand-edit generated route trees as a long-term source of truth.
Pest
Feature and browser tests use Pest 4. See Testing for layout and how to run suites.
composer test
# or focused:
php artisan test --filter=SomeTestName
Coding conventions
Enums are the source of truth
| Concern | Enum | Sync / mirror |
|---|---|---|
| Permissions | App\Enums\PermissionEnum | php artisan permissions:sync → DB + resources/js/enums/permission-enum.ts |
| Roles (built-in names) | App\Enums\RoleEnum | Seeded in RoleSeeder |
| Field types | App\Enums\FieldTypeEnum | Validation, normalizer, UI catalog, AI instructions |
| File kinds | App\Enums\FileTypeEnum | File manager / storage helpers |
Do not invent permission or field-type strings in controllers or React without updating the enum first. Extension recipes: Extending Externa.
Actions & Services
app/Services/— domain orchestration used by controllers, jobs, and AI tools (FileService,Authorization\EffectivePermissionResolver, Collections services, …).app/Actions/— single-purpose actions (Fortify actions live underapp/Actions/Fortify). Prefer a small Action class when a use-case is reused or would bloat a controller.- Controllers stay thin: Form Requests validate/authorize; Services/Actions mutate state; Jobs handle async work.
HTTP & auth
- Admin/files/AI routes: enforce permissions with middleware or Form Requests (
AuthorizesWithPermission/EffectivePermissionResolver). - Collections HTTP currently relies on UI + AI checks — if you harden that, document it and add tests (Collections API).
- AI tools must use
ChecksAiPermissionsfor mutating/domain operations.
Frontend
- Pages under
resources/js/pages/…matching Inertia names. - Gate UI with
useCan()+PermissionEnumfrom the generated TS enum. - Follow existing shadcn/Radix + Tailwind patterns; avoid one-off design systems.
Jobs & schedule
New async work should use the queue (not dispatchSync in request paths for heavy I/O). If you add a scheduled command, register it in routes/console.php and document it under Operations.
Graphify knowledge graph
This monorepo maintains a graphify index under graphify-out/ at the Externa workspace root.
After you change code (PHP/TS that affects architecture or APIs):
cd /path/to/Externa
graphify update .
Use graphify query "…" when exploring relationships before large refactors. Skip graphify update for docs-only or pure config-comment edits that do not change behaviour.
Docs
Product docs live in externa-docs (Markdoc). Match existing frontmatter, {% .lead %}, and callout style. Keep statements accurate to externa-core source — no placeholder lorem.
PR expectations
- Pint / ESLint / Prettier /
tscclean. - Pest coverage for new behaviour (see Testing).
- Enum +
permissions:syncwhen permissions change; commit regenerated TS enum. - Update Markdoc pages when you change public routes or config contracts.
graphify update .when the code graph should reflect your changes.