Files
Portfolio-Game/docs/implementation-artifacts/1-1-initialisation-monorepo-infrastructure.md
skycel ec1ae92799 🎉 Init monorepo Nuxt 4 + Laravel 12 (Story 1.1)
Setup complet de l'infrastructure projet :
- Frontend Nuxt 4 (SSR, TypeScript, i18n, Pinia, TailwindCSS)
- Backend Laravel 12 API-only avec middleware X-API-Key et CORS
- Design tokens (sky-dark, sky-accent, sky-text) et polices (Merriweather, Inter)
- Documentation planning et implementation artifacts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 02:08:56 +01:00

14 KiB

Story 1.1: Initialisation du monorepo et infrastructure

Status: review

Story

As a développeur, I want un projet monorepo Nuxt 4 + Laravel 12 initialisé avec les configurations de base, so that le développement peut commencer sur des fondations solides.

Acceptance Criteria

  1. Given un nouveau repository Git When le projet est initialisé Then la structure monorepo frontend/ (Nuxt 4) + api/ (Laravel 12) est en place
  2. And Nuxt 4 est configuré avec SSR activé, TypeScript, et les modules @nuxtjs/i18n, @nuxtjs/tailwindcss, @pinia/nuxt, nuxt/image, @nuxtjs/sitemap
  3. And Laravel 12 est configuré en mode API-only avec CORS autorisant le domaine frontend
  4. And le middleware API Key (X-API-Key) est en place sur les routes API
  5. And les fichiers .env.example existent pour frontend et backend
  6. And TailwindCSS est configuré avec les design tokens (sky-dark, sky-accent #fa784f, sky-text)
  7. And les polices sont définies (serif narrateur + sans-serif UI)
  8. And le .gitignore est approprié pour les deux applications

Tasks / Subtasks

  • Task 1: Initialisation structure monorepo (AC: #1)

    • Créer le dossier racine skycel/ avec README.md
    • Configurer .gitignore global (node_modules, vendor, .env, etc.)
    • Initialiser Git repository
  • Task 2: Setup Frontend Nuxt 4 (AC: #1, #2)

    • Exécuter npx nuxi@latest init frontend
    • Confirmer structure Nuxt 4 avec dossier app/
    • Activer TypeScript (déjà par défaut dans Nuxt 4)
    • Installer modules: @nuxtjs/i18n, @nuxtjs/tailwindcss, @pinia/nuxt, @nuxt/image, @nuxtjs/sitemap
    • Configurer nuxt.config.ts avec SSR activé
    • Créer frontend/.env.example
  • Task 3: Setup Backend Laravel 12 (AC: #1, #3, #4)

    • Exécuter composer create-project laravel/laravel api
    • Configurer en mode API-only (supprimer views Blade inutiles)
    • Configurer CORS dans config/cors.php pour autoriser le domaine frontend
    • Créer middleware VerifyApiKey pour vérifier header X-API-Key
    • Enregistrer le middleware sur les routes API
    • Créer api/.env.example
  • Task 4: Configuration TailwindCSS avec design tokens (AC: #6)

    • Configurer tailwind.config.js avec thème custom
    • Définir tokens couleurs: sky-dark (noir→bleu), sky-accent (#fa784f), sky-text (blanc cassé)
    • Définir variantes hover/focus pour l'accent
    • Configurer purge pour production
  • Task 5: Configuration des polices (AC: #7)

    • Choisir police serif élégante pour narrateur/PNJ (ex: Merriweather, Lora, Playfair Display)
    • Choisir police sans-serif moderne pour UI (ex: Inter, Open Sans, Nunito)
    • Configurer les polices dans tailwind.config.js (fontFamily)
    • Importer les polices via Google Fonts ou fichiers locaux
  • Task 6: Fichiers .env.example (AC: #5)

    • frontend/.env.example avec: NUXT_PUBLIC_API_URL, NUXT_PUBLIC_API_KEY
    • api/.env.example avec: APP_KEY, DB_*, API_KEY, CORS_ALLOWED_ORIGINS
  • Task 7: Validation finale (AC: tous)

    • cd frontend && npm run dev fonctionne
    • cd api && php artisan serve fonctionne
    • Requête API avec header X-API-Key valide retourne 200
    • Requête API sans header retourne 401
    • Structure des dossiers conforme

Dev Notes

Architecture Monorepo

skycel/
├── frontend/                   # Application Nuxt 4
│   ├── app/                    # Code applicatif (structure Nuxt 4)
│   │   ├── pages/
│   │   ├── components/
│   │   ├── composables/
│   │   ├── stores/
│   │   ├── layouts/
│   │   ├── plugins/
│   │   ├── assets/
│   │   └── app.vue
│   ├── server/                 # Server routes/API Nuxt (si besoin)
│   ├── public/
│   ├── i18n/
│   ├── nuxt.config.ts
│   ├── tailwind.config.js
│   ├── .env.example
│   └── package.json
├── api/                        # Backend Laravel 12
│   ├── app/
│   │   ├── Http/
│   │   │   ├── Controllers/
│   │   │   ├── Middleware/
│   │   │   │   └── VerifyApiKey.php   # CRÉER
│   │   │   ├── Requests/
│   │   │   └── Resources/
│   │   └── Models/
│   ├── database/
│   ├── routes/
│   │   └── api.php
│   ├── config/
│   │   └── cors.php            # CONFIGURER
│   ├── bootstrap/
│   │   └── app.php             # Enregistrer middleware
│   ├── .env.example
│   └── composer.json
├── docs/                       # Documentation projet (existe déjà)
├── .gitignore
└── README.md

Commandes d'initialisation

# Frontend Nuxt 4
npx nuxi@latest init frontend
cd frontend
npm install @nuxtjs/i18n @nuxtjs/tailwindcss @pinia/nuxt @nuxt/image @nuxtjs/sitemap pinia-plugin-persistedstate

# Backend Laravel 12
composer create-project laravel/laravel api
cd api
# Pas de packages supplémentaires pour cette story

Configuration nuxt.config.ts

// frontend/nuxt.config.ts
export default defineNuxtConfig({
  devtools: { enabled: true },

  // SSR activé (défaut)
  ssr: true,

  // Structure Nuxt 4
  future: {
    compatibilityVersion: 4,
  },

  modules: [
    '@nuxtjs/i18n',
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    '@nuxt/image',
    '@nuxtjs/sitemap',
  ],

  // i18n sera configuré en Story 1.3
  i18n: {
    locales: ['fr', 'en'],
    defaultLocale: 'fr',
    strategy: 'prefix_except_default',
  },

  // Transitions de page (configuré en Story 1.4)
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
  },

  runtimeConfig: {
    public: {
      apiUrl: process.env.NUXT_PUBLIC_API_URL || 'http://localhost:8000/api',
      apiKey: process.env.NUXT_PUBLIC_API_KEY || '',
    },
  },
})

Design Tokens TailwindCSS

// frontend/tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{vue,js,ts}',
    './components/**/*.{vue,js,ts}',
    './layouts/**/*.{vue,js,ts}',
    './pages/**/*.{vue,js,ts}',
  ],
  theme: {
    extend: {
      colors: {
        'sky-dark': {
          DEFAULT: '#0a0e1a',      // Noir tirant vers le bleu
          50: '#1a1f2e',
          100: '#151a28',
          200: '#10141f',
          300: '#0c1019',
          400: '#080c14',
          500: '#0a0e1a',          // Base
          600: '#060810',
          700: '#04060c',
          800: '#020408',
          900: '#010204',
        },
        'sky-accent': {
          DEFAULT: '#fa784f',      // Orange chaud
          hover: '#fb8c68',
          active: '#f96436',
          50: '#fff4f0',
          100: '#ffe8e0',
          200: '#ffd1c1',
          300: '#ffb9a2',
          400: '#fca283',
          500: '#fa784f',          // Base
          600: '#e86940',
          700: '#d65a31',
          800: '#c44b22',
          900: '#b23c13',
        },
        'sky-text': {
          DEFAULT: '#f5f0e6',      // Blanc cassé tirant vers jaune
          muted: '#b8b3a8',
          50: '#fdfcfa',
          100: '#fbf9f5',
          200: '#f7f3eb',
          300: '#f5f0e6',          // Base
          400: '#e8e3d9',
          500: '#dbd6cc',
          600: '#cec9bf',
          700: '#c1bcb2',
          800: '#b4afa5',
          900: '#a7a298',
        },
      },
      fontFamily: {
        // Police narrative (serif) - pour narrateur, PNJ, dialogues
        'narrative': ['Merriweather', 'Georgia', 'serif'],
        // Police UI (sans-serif) - pour interface, boutons, labels
        'ui': ['Inter', 'system-ui', 'sans-serif'],
      },
    },
  },
  plugins: [],
}

Middleware Laravel VerifyApiKey

<?php
// api/app/Http/Middleware/VerifyApiKey.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class VerifyApiKey
{
    public function handle(Request $request, Closure $next): Response
    {
        $apiKey = $request->header('X-API-Key');

        if (!$apiKey || $apiKey !== config('app.api_key')) {
            return response()->json([
                'error' => [
                    'code' => 'INVALID_API_KEY',
                    'message' => 'Invalid or missing API key',
                ]
            ], 401);
        }

        return $next($request);
    }
}

Configuration CORS Laravel

<?php
// api/config/cors.php

return [
    'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')),
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => false,
];

Variables d'environnement

frontend/.env.example:

# API Configuration
NUXT_PUBLIC_API_URL=http://localhost:8000/api
NUXT_PUBLIC_API_KEY=your-api-key-here

# Site URL (for sitemap, SEO)
NUXT_PUBLIC_SITE_URL=http://localhost:3000

api/.env.example:

APP_NAME=Skycel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=skycel
DB_USERNAME=root
DB_PASSWORD=

# API Security
API_KEY=your-api-key-here

# CORS
CORS_ALLOWED_ORIGINS=http://localhost:3000

Project Structure Notes

  • Nuxt 4 utilise la nouvelle structure app/ (pas src/)
  • Les composants dans app/components/ sont auto-importés
  • Les composables dans app/composables/ sont auto-importés
  • Les stores Pinia dans app/stores/ sont accessibles via auto-import
  • Composants client-only: utiliser le suffixe .client.vue (pas de SSR)

References

  • [Source: docs/planning-artifacts/architecture.md#Starter-Template-Evaluation]
  • [Source: docs/planning-artifacts/architecture.md#Structure-Monorepo]
  • [Source: docs/planning-artifacts/architecture.md#Authentication-&-Security]
  • [Source: docs/planning-artifacts/ux-design-specification.md#Color-System]
  • [Source: docs/planning-artifacts/ux-design-specification.md#Typography-System]
  • [Source: docs/planning-artifacts/epics.md#Story-1.1]

Technical Requirements

Requirement Value Source
Nuxt version 4.x (latest) Architecture
Laravel version 12.x Architecture
Node.js 18+ Nuxt 4 requirement
PHP 8.2+ Laravel 12 requirement
TypeScript Enabled Architecture
SSR Enabled Architecture, NFR5

Libraries to Install

Frontend (npm):

Package Version Purpose
@nuxtjs/i18n 8.x Internationalisation
@nuxtjs/tailwindcss 6.x Styling
@pinia/nuxt 0.5.x State management
@nuxt/image 1.x Image optimization
@nuxtjs/sitemap 5.x SEO sitemap
pinia-plugin-persistedstate 3.x LocalStorage persistence

Backend (composer):

  • Aucun package supplémentaire pour cette story (Laravel de base suffit)

Dev Agent Record

Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

Debug Log References

  • TailwindCSS: Le module @nuxtjs/tailwindcss v6.14 provoquait une erreur PostCSS Cannot use 'import.meta' outside a module sur Node.js 18. Résolu en remplaçant le module par une configuration PostCSS directe dans nuxt.config.ts.
  • PHP: Le PHP en PATH (8.0.3) est incompatible avec Laravel 12. Utilisation de PHP 8.2.29 disponible dans Laragon pour la création du projet et l'exécution.
  • pinia-plugin-persistedstate: La v4 requiert pinia 3+, incompatible avec @pinia/nuxt 0.9.0 (pinia 2). Downgrade vers v3.2.

Completion Notes List

  • Structure monorepo frontend/ + api/ créée et fonctionnelle
  • Nuxt 4 (3.17.5) configuré avec SSR, TypeScript, i18n, Pinia, @nuxt/image, sitemap
  • TailwindCSS v3 configuré via PostCSS avec design tokens (sky-dark, sky-accent, sky-text)
  • Polices Merriweather (narrative) et Inter (UI) importées via Google Fonts
  • Laravel 12.50 installé en mode API-only avec CORS et middleware VerifyApiKey
  • Middleware API Key vérifié : 401 sans clé, 200 avec clé valide
  • Fichiers .env.example créés pour frontend et backend

Change Log

Date Change Author
2026-02-03 Story créée avec contexte complet SM Agent
2026-02-05 Implémentation complète de toutes les tâches (Tasks 1-7) Dev Agent (Claude Opus 4.5)

File List

Nouveaux fichiers :

  • README.md - Documentation racine du monorepo
  • .gitignore - Gitignore global
  • frontend/package.json - Dependencies Nuxt 4
  • frontend/nuxt.config.ts - Configuration Nuxt 4 avec SSR, modules, PostCSS
  • frontend/tsconfig.json - Config TypeScript
  • frontend/tailwind.config.js - Design tokens TailwindCSS
  • frontend/app/app.vue - Composant racine Vue
  • frontend/app/pages/index.vue - Page d'accueil placeholder
  • frontend/app/assets/css/main.css - CSS global avec import polices
  • frontend/.env.example - Variables d'environnement frontend
  • api/ - Projet Laravel 12 complet (via composer create-project)
  • api/app/Http/Middleware/VerifyApiKey.php - Middleware authentification API Key
  • api/config/cors.php - Configuration CORS
  • api/routes/api.php - Routes API avec endpoint /health

Fichiers modifiés :

  • api/bootstrap/app.php - Routing API-only, enregistrement middleware VerifyApiKey
  • api/config/app.php - Ajout config api_key
  • api/.env.example - Ajout APP_NAME=Skycel, DB config MySQL, API_KEY, CORS_ALLOWED_ORIGINS
  • api/.env - Mêmes ajouts que .env.example avec valeurs dev
  • api/routes/web.php - Vidé (mode API-only)