Compare commits

...

2 commits

Author SHA1 Message Date
a225433cec
add project ai instructions
Document Attainly-specific TDD, worktree, Laravel, database, and future Vue conventions adapted from the youngstartup workflow.
2026-07-30 22:54:39 +03:00
83ca2cf43c
add nix development environment
Provide a direnv-loaded Laravel and future Vue toolchain with isolated worktree ports and a process-compose stack. Use PostgreSQL for local runtime services while retaining in-memory SQLite for PHPUnit.
2026-07-30 22:54:20 +03:00
16 changed files with 724 additions and 13 deletions

11
.envrc Normal file
View file

@ -0,0 +1,11 @@
# Load the flake environment
use flake
onefetch
# Use PHP and Node layouts
layout php
layout node
# Reload when backend dependencies change
watch_file backend/composer.json

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/.cert
/.direnv/
/.mailpit.log
/.postgres/
/.worktrees/

7
AGENTS.md Normal file
View file

@ -0,0 +1,7 @@
# Project context
Read these on every session. Rules in them override defaults.
@ai/shared.md
@ai/backend-context.md
@ai/frontend-context.md

17
Caddyfile Normal file
View file

@ -0,0 +1,17 @@
{
auto_https off
admin off
}
localhost:{$CADDY_PORT:8000}, 127.0.0.1:{$CADDY_PORT:8000} {
tls .cert/localhost.pem .cert/localhost-key.pem
handle /storage/* {
root * backend/public
file_server
}
handle {
reverse_proxy 127.0.0.1:{$BACKEND_PORT:8001}
}
}

105
ai/backend-context.md Normal file
View file

@ -0,0 +1,105 @@
# Backend context
Read `ai/shared.md` first. This file covers backend-specific rules.
## Project context
**Stack:** PHP 8.4, Laravel 13, Inertia Laravel, PHPUnit, Larastan, Composer.
**Location:** `backend/`.
The application is still close to the Laravel starter structure. Do not
introduce a domain architecture, repository layer, service layer, or other
abstraction before the codebase and requested behavior justify it. Match
existing Laravel conventions and inspect similar code before adding a new
pattern.
## Laravel patterns
- Keep controllers thin. Put reusable business behavior in an appropriately
named application or domain class once the behavior warrants extraction.
- Use dedicated request validation rather than validating substantial payloads
inline in controllers.
- Let unexpected exceptions reach Laravel's exception handler. Catch only
exceptions that can be handled meaningfully at the current boundary.
- Use Eloquent relationships and query scopes consistently rather than
duplicating query fragments.
- Avoid speculative interfaces and abstractions with only one trivial
implementation.
- Routes currently use Inertia, but the Vue client has not been scaffolded.
Do not add placeholder frontend assets as part of unrelated backend work.
## Tests
- Follow the existing PHPUnit organization under `tests/Unit/` and
`tests/Feature/`.
- Prefer `PHPUnit\Framework\TestCase` when a test only exercises plain PHP.
- Extend `Tests\TestCase` only when the test needs Laravel's container,
facades, database, routing, or HTTP kernel.
- HTTP feature tests extend `Tests\TestCase`.
- Use `RefreshDatabase` when a test reads or writes database state.
- Assert behavior at the appropriate seam:
- Unit tests cover isolated business behavior and edge cases.
- Feature tests cover routing, middleware, validation, persistence, and
response shape.
- Do not duplicate every business branch through the HTTP layer when unit
coverage already proves it. Feature tests should focus on wiring and the
public contract.
## Test database
- Development and runtime use PostgreSQL through the local Unix socket.
- PHPUnit intentionally uses SQLite `:memory:` as configured in
`phpunit.xml`.
- Feature tests are self-contained and do not require the process-compose
PostgreSQL service.
- Never point `RefreshDatabase` tests at the development PostgreSQL database.
- Keep mail set to the PHPUnit `array` transport unless a test explicitly
exercises a real mail integration.
## PHP rules
- Put imports at the top of the file. Do not use inline fully qualified class
names when a normal `use` statement is clearer.
- Do not use arrow functions. Use regular anonymous functions.
- Do not add default values to function or constructor parameters. Pass every
argument explicitly, including nullable arguments.
- Use descriptive names for classes, methods, parameters, and local
variables.
- Document exceptions with `@throws` when a caller is expected to handle
them.
## Seeders
- Keep `DatabaseSeeder` as the orchestrator.
- Split substantial seed data into one seeder per domain concept and invoke
them in dependency order.
- Do not add production repository or model APIs solely to make seeding
convenient.
- Use existing lookup methods for cross-seeder relationships. When a group of
records only makes sense together, keep them in one seeder and retain local
references.
## Migrations
- Attainly is not in production yet.
- While no production database exists, edit the original `create_*` migration
when changing a table instead of accumulating follow-up alter migrations.
- Keep one migration file per table during this pre-production phase.
- Rebuild the development database with:
```sh
php artisan migrate:fresh --seed
```
- Once a production database exists, replace this policy with additive,
forward-only migrations.
## Before completing backend work
- Run the focused test during development.
- Run `php artisan test` before completion.
- Run the Composer lint and static-analysis scripts when their dependencies
are available.
- Fix failures caused by the change. Report unrelated baseline failures
precisely rather than hiding them or expanding scope without authorization.

90
ai/frontend-context.md Normal file
View file

@ -0,0 +1,90 @@
# Frontend context
Read `ai/shared.md` first. This file covers frontend-specific rules.
## Current state
The Vue frontend has not been scaffolded yet. The backend has Inertia Laravel
installed and an Inertia route, but there is no `package.json`, Vue source
tree, Vite configuration, or frontend test setup.
- Do not create the frontend unless the user explicitly asks.
- Do not invent a frontend directory, package manager, dependency version, or
command before the scaffold establishes it.
- When the frontend is created, update this file with its actual paths,
package versions, scripts, and testing tools.
## Vue conventions
Apply these rules once a Vue 3 frontend exists:
- Use the Composition API and `<script setup lang="ts">`.
- Use PascalCase component filenames.
- Keep page, component, composable, and store responsibilities distinct.
- Prefer small components with explicit props and emitted events.
- Keep component styles scoped unless the scaffold establishes a deliberate
global styling system.
- Follow the routing and page conventions established by the chosen Inertia
or Vue Router scaffold. Do not mix the two approaches without an explicit
architectural reason.
- Inspect similar files before introducing a new component, composable, store,
or data-access pattern.
## TypeScript
- Enable and preserve strict mode.
- Do not use `any`. Model unknown external values as `unknown` and narrow or
validate them.
- Derive types from runtime schemas when a schema library is adopted. Do not
maintain a hand-written type that can drift from its validation schema.
- Validate payloads at trust boundaries, especially backend responses and
user-submitted forms.
- Keep request and response types close to the API or store boundary that owns
them.
- Add a path alias only when it is configured consistently in Vite,
TypeScript, tests, and Cypress.
## State and API access
- Use the state-management approach chosen by the scaffold consistently.
- Keep server data fetching and transformation at an API/store boundary, not
scattered through presentation components.
- Represent loading, empty, success, validation-error, and unexpected-error
states explicitly.
- Do not cast unchecked JSON directly to an application interface.
- Keep read and write payload types separate when their shapes differ.
## Testing
Use layered tests once the frontend test setup exists:
- Unit tests cover pure transformations, composables, store logic, computed
values, and formatting.
- Component tests cover rendering, events, form behavior, and conditional UI.
- Cypress tests cover routing, multi-page flows, and request wiring.
- Prefer the cheapest layer that proves the behavior.
- Import test functions explicitly rather than relying on globals unless the
scaffold deliberately configures globals.
- Build test data with small typed builders instead of repeated bare object
literals.
## Frontend/backend test boundary
- Mock backend requests in frontend tests.
- A frontend test should verify how the UI consumes a response and which
request it sends, not retest Laravel's business rules.
- Test backend persistence, validation, authentication, and mail behavior in
PHPUnit.
- Keep mocked response bodies typed against the frontend's exported API or
store types.
- When runtime schemas exist, parse mock-builder output through the same
schema so drift fails at the test boundary.
## Before completing frontend work
- Run the formatter, linter, type checker, unit tests, and Cypress scripts
defined by the frontend's `package.json`.
- Do not substitute a hand-picked subset for the repository's eventual full
frontend gate.
- If the frontend cannot run because the scaffold or a required service is
absent, report the precise environmental or baseline failure.

177
ai/shared.md Normal file
View file

@ -0,0 +1,177 @@
# Shared rules
Rules that apply to both backend and frontend work in this repository.
Stack-specific guides (`backend-context.md`, `frontend-context.md`) extend
these rules.
## Project state
- Attainly is in early development.
- Attainly helps users break hierarchical goals into scheduled assignments,
complete daily work, and track progress toward a target date.
- Schedule recalculation must preserve completed work while redistributing
unfinished assignments across the remaining dates.
- Planned features in `README.md` are future ideas, not authorized scope.
- The Laravel backend exists under `backend/`.
- The Vue frontend has not been scaffolded yet. Do not create it unless the
user explicitly asks for that work.
- PostgreSQL is the development and runtime database.
- PHPUnit uses in-memory SQLite for isolated tests.
## Process (TDD)
0. Before editing any file, ensure you are working in a dedicated git
worktree, not the main checkout (`git status` and `git worktree list`).
If on `master`/`main` or in the main working directory, create a worktree
first (see Branching).
1. Write the test first.
2. Run the test to confirm it fails for the expected reason.
3. Commit the failing test. The test commit must precede the implementation
commit, not merely appear earlier in the implementation diff.
4. Implement the smallest change that makes the test pass.
5. Run the test to confirm it passes.
6. Commit the implementation.
7. Repeat for each new behavior.
Use judgment for changes that cannot meaningfully be test-driven, such as
documentation-only edits or declarative environment configuration. Validate
those changes with the most relevant parser, formatter, dry run, or check.
## Running processes
- The main checkout owns the canonical stack on the default ports. Assume it
is the user's stack. Do not start, restart, or stop it unless the user asks.
- A worktree owns its own isolated stack. The flake shell hook assigns a
deterministic port offset and creates worktree-local PostgreSQL state.
- Start a worktree stack from its root with `process-compose up`. For
non-interactive use, run `process-compose up -D` and stop it with
`process-compose down`.
- Do not use `process-compose -t=false` for a detached stack. It can leave an
orphaned PostgreSQL process holding the data directory.
- Non-interactive agent shells do not automatically load direnv. A bare
`process-compose`, `php artisan`, or database command from a worktree can
silently use default ports and target the main checkout.
- Prefix worktree stack and database commands with `direnv exec <worktree>`.
Examples:
```sh
direnv exec <worktree> process-compose up -D
direnv exec <worktree> process-compose down
direnv exec <worktree> php artisan migrate:fresh --seed
```
- Run backend commands from `backend/`, or explicitly change into it in the
command.
- When a normally valid check fails because a required service is down,
surface the environmental failure. Do not skip the check or silently switch
to a different database or service.
- PHPUnit is self-contained and uses in-memory SQLite. It does not require the
PostgreSQL stack unless a future test is explicitly designed as a real
PostgreSQL integration test.
## Code style
- Keep lines at or below 80 columns where practical. Do not split short,
readable lines unnecessarily.
- Use explicit, descriptive variable names. Do not use single-letter or
unexplained abbreviated names.
- Explore the codebase and inspect similar files before implementing a new
pattern.
- Never use em dashes in code, comments, or docblocks. Use hyphens.
- Always use braced control-statement bodies, including early returns:
```ts
if (identifier === null) {
return null
}
```
## Git commit style
- Use present-tense, imperative subjects: `add`, `create`, `wire`, `fix`,
`test`.
- Keep subjects lowercase and short, normally three to six words.
- Match patterns in the existing git history.
- Do not add AI or tool coauthor trailers.
- Add a body when the subject cannot explain non-obvious motivation or
multi-file coordination.
- Wrap bodies at approximately 72 columns and separate them from the subject
with a blank line.
## Git commits
- Commit tests before implementation.
- Keep one logical change per commit. A logical change may span multiple
files.
- Commit each meaningful step rather than batching unrelated work.
- Do not include drive-by formatter or linter changes. Restore them or land
them as a separate formatting commit.
- If a check fails on untouched code, do not bundle an unrelated fix. Report
the pre-existing failure or handle it as its own explicitly scoped change.
## Branching
- Never implement features directly in the main checkout or on
`master`/`main`.
- Use a dedicated worktree under
`<repo-root>/.worktrees/<branch>`.
- Create it with:
```sh
git worktree add \
"$(git rev-parse --show-toplevel)/.worktrees/<branch>" \
-b <branch>
```
- Use descriptive kebab-case branch names, optionally prefixed with a type
such as `feature/` or `fix/`.
- Keep worktree names short, preferably no more than about 20 characters.
PostgreSQL Unix socket paths are limited in length.
- Provision a fresh worktree through direnv:
```sh
direnv allow <worktree>
direnv exec <worktree> true
```
- Never symlink `backend/vendor` or a future frontend's `node_modules` from
another checkout. Dependency paths and generated autoloaders must remain
worktree-local.
Do not push anything. Make commits as the TDD workflow requires.
## Before completing a change
Run the smallest relevant checks while iterating, then run every repository
gate affected by the change.
### Backend
- Run tests from `backend/` with `php artisan test`.
- Run the Composer checks defined by `backend/composer.json` when their
dependencies are available:
```sh
composer lint:check
composer types:check
composer test
```
- Do not claim a green gate when a command fails. If the failure predates the
change, report the precise baseline failure.
### Frontend
- The frontend does not exist yet. Once scaffolded, use the scripts defined in
its `package.json` for formatting, linting, type checking, unit tests, and
end-to-end tests.
- Update `ai/frontend-context.md` when the actual scaffold, package manager,
and commands are known.
### Environment and integration
- For Nix or shell-hook changes, run `nix fmt` and `nix flake check`.
- For service configuration changes, run `process-compose --dry-run`.
- When a change affects runtime wiring, start the worktree's stack and verify
the relevant endpoint or service against that worktree.
- If you started the stack only for validation, stop it before finishing.

View file

@ -2,7 +2,7 @@ APP_NAME=Laravel
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost:8000 APP_URL=https://localhost:8000
APP_LOCALE=en APP_LOCALE=en
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en
@ -20,12 +20,12 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=sqlite DB_CONNECTION=pgsql
# DB_HOST=127.0.0.1 DB_HOST="${PGHOST}"
# DB_PORT=3306 DB_PORT=5432
# DB_DATABASE=laravel DB_DATABASE=postgres
# DB_USERNAME=root DB_USERNAME=postgres
# DB_PASSWORD= DB_PASSWORD=
SESSION_DRIVER=database SESSION_DRIVER=database
SESSION_LIFETIME=120 SESSION_LIFETIME=120
@ -47,7 +47,7 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_MAILER=log MAIL_MAILER=smtp
MAIL_SCHEME=null MAIL_SCHEME=null
MAIL_HOST=127.0.0.1 MAIL_HOST=127.0.0.1
MAIL_PORT=2525 MAIL_PORT=2525

View file

@ -81,7 +81,6 @@
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi", "@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi" "@php artisan migrate --graceful --ansi"
], ],
"pre-package-uninstall": [ "pre-package-uninstall": [

View file

@ -17,7 +17,7 @@ return [
| |
*/ */
'default' => env('DB_CONNECTION', 'sqlite'), 'default' => env('DB_CONNECTION', 'pgsql'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -103,7 +103,7 @@ return [
*/ */
'batching' => [ 'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'), 'database' => env('DB_CONNECTION', 'pgsql'),
'table' => 'job_batches', 'table' => 'job_batches',
], ],
@ -122,7 +122,7 @@ return [
'failed' => [ 'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'), 'database' => env('DB_CONNECTION', 'pgsql'),
'table' => 'failed_jobs', 'table' => 'failed_jobs',
], ],

View file

@ -1 +0,0 @@
*.sqlite*

61
flake.lock generated Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1785318670,
"narHash": "sha256-dN6Ou5x/+23FZLEpYP3IffO+NyJFzUlGumt1uu3MMaY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0954f7ee2f6bb3dc7d4e3d0d8bcb8fd4bde4cfc5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

54
flake.nix Normal file
View file

@ -0,0 +1,54 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
utils,
}:
utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
php = pkgs.php.buildEnv {
extraConfig = ''
memory_limit = "1G";
upload_max_filesize = "10M";
post_max_size = "25M";
'';
};
in
{
formatter = pkgs.nixfmt-tree;
devShells.default = pkgs.mkShell {
packages = with pkgs; [
bash
onefetch
just
php
phpPackages.composer
phpPackages.php-codesniffer
vscode-langservers-extracted
nodejs
nixfmt
nixfmt-tree
cypress
yaml-language-server
typescript
postgresql
mailpit
process-compose
mkcert
caddy
];
shellHook = builtins.readFile ./nix/shell-hook.sh;
};
}
);
}

127
nix/shell-hook.sh Normal file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env bash
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
GIT_DIR_PATH="$(git rev-parse --git-dir 2>/dev/null)"
GIT_COMMON_PATH="$(git rev-parse --git-common-dir 2>/dev/null)"
# Linked worktrees get their own runtime state and port range.
if [ -n "$GIT_COMMON_PATH" ] \
&& [ "$GIT_DIR_PATH" != "$GIT_COMMON_PATH" ]; then
IS_WORKTREE=1
MAIN_REPO_ROOT="$(cd "$GIT_COMMON_PATH/.." && pwd)"
else
IS_WORKTREE=0
MAIN_REPO_ROOT="$REPO_ROOT"
fi
if [ "$IS_WORKTREE" = 1 ]; then
if [ -n "$PORT_OFFSET" ]; then
OFFSET="$PORT_OFFSET"
else
PORT_HASH="$(printf '%s' "$REPO_ROOT" | cksum | cut -d' ' -f1)"
OFFSET=$(( (PORT_HASH % 49 + 1) * 100 ))
fi
else
OFFSET=0
fi
export CADDY_PORT=$((8000 + OFFSET))
export BACKEND_PORT=$((8001 + OFFSET))
export VITE_PORT=$((5173 + OFFSET))
export MAILPIT_SMTP_PORT=$((2525 + OFFSET))
export MAILPIT_UI_PORT=$((8025 + OFFSET))
export PC_PORT_NUM=$((8080 + OFFSET))
export PGDATA="$REPO_ROOT/.postgres"
export PGHOST="$PGDATA"
export PGUSER="postgres"
export PGDATABASE="postgres"
DEV_APP_URL="https://localhost:$CADDY_PORT"
DEV_DB_CONNECTION="pgsql"
DEV_DB_HOST="$PGHOST"
DEV_DB_PORT="5432"
DEV_DB_DATABASE="$PGDATABASE"
DEV_DB_USERNAME="$PGUSER"
DEV_DB_PASSWORD=""
DEV_MAIL_MAILER="smtp"
DEV_MAIL_HOST="127.0.0.1"
DEV_MAIL_PORT="$MAILPIT_SMTP_PORT"
if [ ! -d "$PGDATA" ]; then
echo "[pg] initializing cluster at $PGDATA"
initdb --auth=trust --username="$PGUSER" --no-locale --encoding=UTF8 >/dev/null
{
echo "listen_addresses = ''"
echo "unix_socket_directories = '$PGDATA'"
} >> "$PGDATA/postgresql.conf"
fi
# Worktrees share host-independent localhost certificates with the main checkout.
MAIN_CERT_ROOT="$MAIN_REPO_ROOT/.cert"
if [ ! -d "$MAIN_CERT_ROOT" ]; then
echo "[cert] installing local CA + generating TLS certs in $MAIN_CERT_ROOT"
mkcert -install
mkdir -p "$MAIN_CERT_ROOT"
mkcert \
-key-file "$MAIN_CERT_ROOT/localhost-key.pem" \
-cert-file "$MAIN_CERT_ROOT/localhost.pem" \
localhost 127.0.0.1 ::1
fi
if [ "$IS_WORKTREE" = 1 ] \
&& [ ! -e "$REPO_ROOT/.cert" ] \
&& [ ! -L "$REPO_ROOT/.cert" ]; then
ln -s "$MAIN_CERT_ROOT" "$REPO_ROOT/.cert"
fi
ENV_FILE="$REPO_ROOT/backend/.env"
if [ ! -f "$ENV_FILE" ]; then
if [ "$IS_WORKTREE" = 1 ] && [ -f "$MAIN_REPO_ROOT/backend/.env" ]; then
cp "$MAIN_REPO_ROOT/backend/.env" "$ENV_FILE"
else
cp "$REPO_ROOT/backend/.env.example" "$ENV_FILE"
fi
fi
set_env_value() {
local env_key="$1"
local env_value="$2"
local escaped_value
escaped_value="$(printf '%s' "$env_value" | sed 's/[&|\\]/\\&/g')"
if grep -q "^${env_key}=" "$ENV_FILE"; then
sed -i "s|^${env_key}=.*|${env_key}=${escaped_value}|" "$ENV_FILE"
else
printf '%s=%s\n' "$env_key" "$env_value" >> "$ENV_FILE"
fi
}
set_env_value APP_URL "$DEV_APP_URL"
set_env_value DB_CONNECTION "$DEV_DB_CONNECTION"
set_env_value DB_HOST "$DEV_DB_HOST"
set_env_value DB_PORT "$DEV_DB_PORT"
set_env_value DB_DATABASE "$DEV_DB_DATABASE"
set_env_value DB_USERNAME "$DEV_DB_USERNAME"
set_env_value DB_PASSWORD "$DEV_DB_PASSWORD"
set_env_value MAIL_MAILER "$DEV_MAIL_MAILER"
set_env_value MAIL_HOST "$DEV_MAIL_HOST"
set_env_value MAIL_PORT "$DEV_MAIL_PORT"
if [ ! -d "$REPO_ROOT/backend/vendor" ]; then
echo "[composer] installing backend dependencies"
(cd "$REPO_ROOT/backend" && composer install)
fi
if grep -q '^APP_KEY=$' "$ENV_FILE"; then
echo "[laravel] generating application key"
(cd "$REPO_ROOT/backend" && php artisan key:generate)
fi
if [ ! -e "$REPO_ROOT/backend/public/storage" ]; then
echo "[laravel] linking public storage"
(cd "$REPO_ROOT/backend" && php artisan storage:link)
fi
echo "[dev] run 'process-compose up' to start postgres + mailpit + backend + caddy"

59
process-compose.yaml Normal file
View file

@ -0,0 +1,59 @@
version: "0.5"
processes:
postgres:
command: postgres -D "$PGDATA" -k "$PGDATA" -c listen_addresses=''
shutdown:
signal: 2
readiness_probe:
exec:
command: pg_isready -h "$PGDATA" -d "$PGDATABASE" -U "$PGUSER"
initial_delay_seconds: 1
period_seconds: 2
migrate:
command: php artisan migrate --force
working_dir: ./backend
depends_on:
postgres:
condition: process_healthy
mailpit:
command: mailpit --smtp 127.0.0.1:${MAILPIT_SMTP_PORT:-2525} --listen 127.0.0.1:${MAILPIT_UI_PORT:-8025}
readiness_probe:
http_get:
host: 127.0.0.1
port: ${MAILPIT_UI_PORT:-8025}
path: /
initial_delay_seconds: 1
period_seconds: 2
backend:
command: php artisan serve --host=127.0.0.1 --port=${BACKEND_PORT:-8001}
working_dir: ./backend
depends_on:
migrate:
condition: process_completed_successfully
mailpit:
condition: process_healthy
readiness_probe:
http_get:
host: 127.0.0.1
port: ${BACKEND_PORT:-8001}
path: /up
initial_delay_seconds: 2
period_seconds: 10
caddy:
command: caddy run --config Caddyfile --adapter caddyfile
depends_on:
backend:
condition: process_healthy
readiness_probe:
http_get:
host: 127.0.0.1
port: ${CADDY_PORT:-8000}
path: /up
scheme: https
initial_delay_seconds: 1
period_seconds: 2