Files
InvoiceShelf/CLAUDE.md
Darko Gjorgjijoski 2f641ace0e build: migrate frontend tooling to pnpm + pin vite 8.0.3 (#666)
* build: migrate frontend tooling from yarn to pnpm

The Dockerfiles ran `yarn && yarn build`, which broke on node:24 (yarn no
longer on PATH; the corepack yarn shim made `npm i -g yarn` fail EEXIST),
while CI + Makefile used npm and only a yarn.lock was committed — an
inconsistent yarn/npm split. Standardize on pnpm, pinned via the
packageManager field + corepack.

- package.json: packageManager pnpm@11.6.0.
- pnpm-workspace.yaml: nodeLinker: hoisted (flat node_modules, npm/yarn-like, so
  directly-imported transitive deps like flatpickr resolve) + allow vue-demi's
  postinstall (it selects the Vue 3 entry). pnpm 11 reads these here, not .npmrc.
- Generate pnpm-lock.yaml (imported from yarn.lock); delete yarn.lock.
- Dockerfiles (dev/nginx/production): node:24 + `corepack enable && pnpm install --frozen-lockfile && pnpm build`.
- CI (check.yaml, docker.yaml): pnpm/action-setup + setup-node cache:pnpm; pnpm install --frozen-lockfile / pnpm build.
- Makefile, composer.json dev script, CLAUDE.md: npm/yarn -> pnpm.

pnpm build verified on a clean install (1425 modules, hoisted node_modules).

* fix(build): pin vite to 8.0.3 to fix rolldown chunk regression

vite 8.0.16 (pulled in by #653) bundles rolldown 1.0.3, which emits a
lazy chunk referencing an undefined Vue runtime-init function
(init_runtime_dom_esm_bundler), breaking the SPA at runtime. The build
succeeds so CI never caught it. Pin vite to 8.0.3 (the version 2.3.3
shipped, rolldown 1.0.0) which produces a correct bundle.
2026-06-12 09:31:08 +02:00

3.8 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

InvoiceShelf is an open-source invoicing and expense tracking application built with Laravel 13 (PHP 8.4) and Vue 3. It supports multi-company tenancy, customer portals, recurring invoices, and PDF generation.

Common Commands

Development

composer run dev          # Starts PHP server, queue listener, log tail, and Vite dev server concurrently
pnpm dev                  # Vite dev server only
pnpm build                # Production frontend build

Testing

php artisan test --compact                        # Run all tests
php artisan test --compact --filter=testName       # Run specific test
./vendor/bin/pest --stop-on-failure                # Run via Pest directly
make test                                          # Makefile shortcut

Tests use SQLite in-memory DB, configured in phpunit.xml. Tests seed via DatabaseSeeder + DemoSeeder in beforeEach. Authenticate with Sanctum::actingAs() and set the company header.

Code Style

vendor/bin/pint --dirty --format agent    # Fix style on modified PHP files
vendor/bin/pint --test                    # Check style without fixing (CI uses this)

Artisan Generators

Always use php artisan make:* with --no-interaction to create new files (models, controllers, migrations, tests, etc.).

Architecture

Multi-Tenancy

Every major model has a company_id foreign key. The CompanyMiddleware sets the active company from the company request header. Bouncer authorization is scoped to the company level via DefaultScope (app/Bouncer/Scopes/DefaultScope.php).

Authentication

Three guards: web (session), api (Sanctum tokens for /api/v1/), customer (session for customer portal). API routes use auth:sanctum middleware; customer portal uses auth:customer.

Routing

  • API: All endpoints under /api/v1/ in routes/api.php, grouped with auth:sanctum, company, and bouncer middleware
  • Web: routes/web.php serves PDF endpoints, auth pages, and catch-all SPA routes (/admin/{vue?}, /{company:slug}/customer/{vue?})

Frontend

  • Entry point: resources/scripts/main.js
  • Vue Router: resources/scripts/admin/admin-router.js (admin), resources/scripts/customer/customer-router.js (customer portal)
  • State: Pinia stores in resources/scripts/admin/stores/
  • Path aliases: @ = resources/, $fonts, $images for static assets
  • Vite dev server expects invoiceshelf.test hostname

Backend Patterns

  • Authorization: Silber/Bouncer with policies in app/Policies/. Controllers use $this->authorize().
  • Validation: Form Request classes, never inline validation
  • API responses: Eloquent API Resources in app/Http/Resources/
  • PDF generation: DomPDF (GeneratesPdfTrait) or Gotenberg
  • Email: Mailable classes with EmailLog tracking
  • File storage: Spatie MediaLibrary, supports local/S3/Dropbox
  • Serial numbers: SerialNumberFormatter service
  • Company settings: CompanySetting model (key-value per company)

Database

Supports MySQL, PostgreSQL, and SQLite. Prefer Eloquent over raw queries. Use Model::query() instead of DB::. Use eager loading to prevent N+1 queries.

Code Conventions

  • PHP: snake_case, constructor property promotion, explicit return types, PHPDoc blocks over inline comments
  • JS: camelCase
  • Always check sibling files for patterns before creating new ones
  • Use config() helper, never env() outside config files
  • Every change must have tests (feature tests preferred over unit tests)
  • Run vendor/bin/pint --dirty --format agent after modifying PHP files

CI Pipeline

GitHub Actions (check.yaml): runs Pint style check, then builds frontend and runs Pest tests on PHP 8.4.