Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- Rename project name across code, configs, docs, dev/prod infra - Dev: DOCKER_APP_NAME + POSTGRES_DB switched to starseed, containers become php-starseed-fpm / nginx-starseed / starseed-db-1 - Dev: mount nginx.conf on default.conf instead of starseed.conf to avoid alphabetical-order clash with image's default site - Makefile: export CURRENT_UID/CURRENT_GID at top level so docker compose builds (db-reset etc.) get them - Prod: image registry path, container_name, volumes, vhost server_name + paths, DATABASE_URL, CORS, CI workflow - Add doc/prompt-rename-prod.md with the migration runbook for the prod server (DB rename, FS move, vhost, Let's Encrypt)
209 lines
9.1 KiB
Markdown
209 lines
9.1 KiB
Markdown
---
|
|
name: create-module
|
|
description: Scaffold a new Starseed module (backend + frontend) and optionally wire its entries into the sidebar config. Use when the user asks to create, add, scaffold, or generate a new module — e.g., "crée un module Paie", "add a Pointage module", "ajoute un module RH". The backend is the source of truth for activation and sidebar layout; the frontend scans modules automatically.
|
|
---
|
|
|
|
# Create a new Starseed module
|
|
|
|
Scaffolds a new module across backend and frontend following Starseed's modular monolith DDD architecture.
|
|
|
|
## Architecture reminder — read before acting
|
|
|
|
The module system has **two concerns that are decoupled**:
|
|
|
|
1. **Module** = code + routes + pages. A module is a bounded context that owns feature code. Declared in `config/modules.php` (backend) and scanned automatically on frontend via `nuxt.config.ts` (any directory under `frontend/modules/` becomes a Nuxt layer).
|
|
2. **Sidebar** = navigation layout. Defined in `config/sidebar.php`. Each sidebar item references the module that owns it via the `module` key. Items whose module is not active are filtered out by the backend. **You can freely move a sidebar item from one section to another without touching module code.**
|
|
|
|
Consequences:
|
|
- Frontend never hardcodes module metadata — no `.module.ts`, no `modules-loader.ts`, no manual registration.
|
|
- Frontend never hardcodes the sidebar — it fetches `GET /api/sidebar`.
|
|
- To add a new module in the sidebar: edit `config/sidebar.php`.
|
|
- To move an item between sections: edit `config/sidebar.php`.
|
|
- To disable a module: remove/comment it from `config/modules.php`. Its sidebar items are auto-filtered AND its routes land in `disabledRoutes`, which the frontend middleware `modules.global.ts` uses to redirect any direct URL access back to `/`.
|
|
|
|
The `/api/sidebar` response shape is:
|
|
```json
|
|
{
|
|
"sections": [{ "label": "...", "icon": "...", "items": [...] }],
|
|
"disabledRoutes": ["/commercial", "/commercial/orders"]
|
|
}
|
|
```
|
|
Any item whose `module` key matches an inactive module is moved from `sections` into `disabledRoutes` automatically by `SidebarProvider`.
|
|
|
|
## When to use
|
|
|
|
User wants to create a new vendable module. Example triggers:
|
|
- "Crée un module Paie"
|
|
- "Ajoute un module Pointage"
|
|
- "Scaffold a new Stock module"
|
|
|
|
## Prerequisites — gather before acting
|
|
|
|
Ask the user (via `AskUserQuestion` if not already provided):
|
|
|
|
1. **Module name** (French, human readable, e.g., "Paie", "Gestion RH", "Pointage"). Derive:
|
|
- `PascalCase` for backend folders/classes: `Paie`, `GestionRh`, `Pointage`
|
|
- `kebab-case` for frontend folders/routes: `paie`, `gestion-rh`, `pointage`
|
|
- `camelCase` for i18n keys: `paie`, `gestionRh`, `pointage`
|
|
- `snake_case` for module ID: `paie`, `gestion_rh`, `pointage`
|
|
2. **Icon** — an mdi icon identifier for the sidebar section (e.g., `mdi:cash-multiple`). If not given, suggest a relevant one.
|
|
3. **Should it appear in the sidebar immediately?** If yes, ask which section (new one or existing) and what nav items (label + route).
|
|
|
|
Don't guess these — ask.
|
|
|
|
## Placeholders used below
|
|
|
|
- `{Pascal}` = PascalCase (e.g., `Paie`)
|
|
- `{kebab}` = kebab-case (e.g., `paie`)
|
|
- `{camel}` = camelCase i18n slug (e.g., `paie`, `gestionRh`)
|
|
- `{id}` = module ID for backend + config (same as `{camel}` for single-word names, `snake_case` otherwise)
|
|
- `{label}` = French display label (e.g., `Paie`)
|
|
- `{icon}` = mdi icon string (e.g., `mdi:cash-multiple`)
|
|
|
|
## Files to create/edit
|
|
|
|
### Backend — module declaration
|
|
|
|
**1. `src/Module/{Pascal}/{Pascal}Module.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\{Pascal};
|
|
|
|
final class {Pascal}Module
|
|
{
|
|
public const string ID = '{id}';
|
|
public const string LABEL = '{label}';
|
|
public const bool REQUIRED = false;
|
|
}
|
|
```
|
|
|
|
**2. Edit `config/modules.php`** — append the module class:
|
|
|
|
```php
|
|
return [
|
|
\App\Module\Core\CoreModule::class,
|
|
// ... existing modules
|
|
\App\Module\{Pascal}\{Pascal}Module::class,
|
|
];
|
|
```
|
|
|
|
### Backend — sidebar config (only if the user wants sidebar entries)
|
|
|
|
**3. Edit `config/sidebar.php`** — either add items to an existing section or append a new section:
|
|
|
|
```php
|
|
[
|
|
'label' => 'sidebar.{camel}.section',
|
|
'icon' => '{icon}',
|
|
'items' => [
|
|
[
|
|
'label' => 'sidebar.{camel}.overview',
|
|
'to' => '/{kebab}',
|
|
'icon' => '{icon}',
|
|
'module' => '{id}',
|
|
],
|
|
// more items from user input...
|
|
],
|
|
],
|
|
```
|
|
|
|
Every item must have a `module` key matching an active module ID — otherwise the backend will filter it out.
|
|
|
|
### Frontend — module code (auto-detected)
|
|
|
|
**4a. `frontend/modules/{kebab}/nuxt.config.ts`** — required for Nuxt to treat the folder as a layer. Content:
|
|
|
|
```typescript
|
|
export default defineNuxtConfig({})
|
|
```
|
|
|
|
**4b. `frontend/modules/{kebab}/pages/{kebab}.vue`** — placeholder page. The filename becomes the route `/{kebab}`.
|
|
|
|
```vue
|
|
<template>
|
|
<div>
|
|
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('{camel}.title') }}</h1>
|
|
<p class="mt-4 text-neutral-500">{{ $t('{camel}.welcome') }}</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const { t } = useI18n()
|
|
|
|
useHead({ title: t('{camel}.title') })
|
|
</script>
|
|
```
|
|
|
|
Create one page file per nav item the user specified. Derive filenames from the `to` paths (e.g., `/paie/bulletins` → `pages/paie/bulletins.vue`).
|
|
|
|
**Do NOT create any of these — they no longer exist in this architecture:**
|
|
- ❌ `frontend/modules/{kebab}/{kebab}.module.ts`
|
|
- ❌ `frontend/plugins/modules-loader.ts`
|
|
- ❌ Any `extends` entry in `nuxt.config.ts` — modules are auto-detected from `frontend/modules/*/`.
|
|
|
|
### Frontend — translations
|
|
|
|
**5. Edit `frontend/i18n/locales/fr.json`** — add the sidebar keys and page content keys:
|
|
|
|
```json
|
|
{
|
|
"sidebar": {
|
|
"{camel}": {
|
|
"section": "{label}",
|
|
"overview": "Vue d'ensemble"
|
|
}
|
|
},
|
|
"{camel}": {
|
|
"title": "{label}",
|
|
"welcome": "Module {label}"
|
|
}
|
|
}
|
|
```
|
|
|
|
Every `label` in the new `config/sidebar.php` entries must have a matching key here, and every page's `$t('{camel}.*')` calls must match too.
|
|
|
|
## Implementation steps
|
|
|
|
Execute in this exact order:
|
|
|
|
1. **Clarify inputs** — use `AskUserQuestion` for name, icon, and sidebar decisions.
|
|
2. **Derive naming** — confirm Pascal/kebab/camel/id are sensible (e.g., "Gestion RH" → `GestionRh`/`gestion-rh`/`gestionRh`/`gestion_rh`).
|
|
3. **Read current state** — Read `config/modules.php`, `config/sidebar.php`, `frontend/i18n/locales/fr.json` so you know what exists before editing.
|
|
4. **Backend: declare the module** — create `{Pascal}Module.php`, edit `config/modules.php`.
|
|
5. **Frontend: create pages** — create the placeholder `.vue` files under `frontend/modules/{kebab}/pages/`.
|
|
6. **Backend: sidebar** — if the user wants sidebar entries, edit `config/sidebar.php`.
|
|
7. **Frontend: translations** — edit `frontend/i18n/locales/fr.json`.
|
|
8. **Verify** — run:
|
|
- `docker exec -t -u root php-starseed-fpm chown -R www-data:www-data /var/www/html/var` (avoid permission issues)
|
|
- `docker exec -t -u www-data php-starseed-fpm php bin/console cache:clear` (validates backend)
|
|
- `cd frontend && npx nuxi prepare` (validates Nuxt auto-detection of the new layer)
|
|
9. **Report** — list files created, the route(s) to test, and the sidebar items added.
|
|
|
|
## Rules — do not violate
|
|
|
|
- **Never create `.module.ts` files** — the old `ModuleDefinition` / `registerModule` pattern is gone. Activation and sidebar are 100% backend-driven.
|
|
- **Never edit `extends` in `nuxt.config.ts`** — it auto-scans `frontend/modules/*/`. Adding entries manually causes duplicates.
|
|
- **Never create `frontend/plugins/modules-loader.ts`** — it was deleted on purpose.
|
|
- **All sidebar labels are i18n keys, not raw text** — the layout calls `t()` on every label. Raw text will display as-is and look broken.
|
|
- **Every sidebar item needs a `module` key** — if omitted, the backend filters it out silently.
|
|
- **Backend module must be in `config/modules.php`** — otherwise `/api/sidebar` will hide its items even though the sidebar config references them.
|
|
- **No DDD scaffolding unless asked** — only create the `{Pascal}Module.php` initially. Don't create empty `Domain/`, `Application/`, `Infrastructure/` folders. Real domain code comes when the module gets features.
|
|
- **Don't create a page per nav item blindly** — if the user didn't specify, create only the root `/{kebab}` page and ask whether they want stubs for subpages.
|
|
- **Don't use `make cache-clear`** — it may hit permission issues. Use the docker commands directly (see Verify step).
|
|
|
|
## Naming derivation examples
|
|
|
|
| User input | Pascal | kebab | camel | id |
|
|
|------------|--------|-------|-------|-----|
|
|
| Paie | Paie | paie | paie | paie |
|
|
| Pointage | Pointage | pointage | pointage | pointage |
|
|
| Gestion RH | GestionRh | gestion-rh | gestionRh | gestion_rh |
|
|
| Stock & Inventaire | StockInventaire | stock-inventaire | stockInventaire | stock_inventaire |
|
|
| CRM | Crm | crm | crm | crm |
|
|
|
|
For accented characters (é, è, à, ç...): strip accents in folder/identifier names (`Forêt` → `Foret`/`foret`), keep them in the French `label`.
|