552 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			552 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 🚀 PLAN D'EXÉCUTION & MÉTRIQUES
 | |
| 
 | |
| ## 1. RÉSUMÉ EXÉCUTABLE — Ordre d'Attaque des P0 (Semaine 1)
 | |
| 
 | |
| ### Jour 1: Sécurité Critique
 | |
| **Objectif:** Éliminer vulnérabilité XSS  
 | |
| **Tasks:**
 | |
| - [ ] Installer DOMPurify: `npm install dompurify @types/dompurify`
 | |
| - [ ] Remplacer `escapeHtml()` par `DOMPurify.sanitize()` dans `MarkdownService`
 | |
| - [ ] Configurer whitelist: `ALLOWED_TAGS`, `ALLOWED_ATTR`
 | |
| - [ ] Tests avec payloads XSS (OWASP Top 10)
 | |
| - [ ] Commit + merge
 | |
| 
 | |
| **Critère de succès:** Payload `<img src=x onerror=alert(1)>` neutralisé
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Jour 2-3: Performance UI Immédiate
 | |
| **Objectif:** Éliminer gels perceptibles  
 | |
| **Tasks:**
 | |
| - [ ] Implémenter CDK Virtual Scroll pour résultats de recherche (2h)
 | |
| - [ ] Ajouter `trackBy` sur toutes les listes `@for` (1h)
 | |
| - [ ] Debounce rebuild index (search + graph) avec `debounceTime(300)` (3h)
 | |
| - [ ] Tests E2E: search 500 notes <150ms (2h)
 | |
| 
 | |
| **Critère de succès:** Aucun gel UI >100ms sur actions utilisateur
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Jour 4-5: Offload Computation
 | |
| **Objectif:** Libérer main thread  
 | |
| **Tasks:**
 | |
| - [ ] Créer `markdown.worker.ts` avec MarkdownIt (4h)
 | |
| - [ ] Implémenter `MarkdownWorkerService` avec pool 2 workers (3h)
 | |
| - [ ] Lazy load Mermaid + `runOutsideAngular()` (2h)
 | |
| - [ ] Lazy load MathJax (1h)
 | |
| - [ ] Tests rendering note 1000 lignes + mermaid (2h)
 | |
| 
 | |
| **Critère de succès:** Parsing note complexe: main thread <16ms
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Jour 6-7: Backend Meilisearch MVP
 | |
| **Objectif:** Recherche scalable  
 | |
| **Tasks:**
 | |
| - [ ] Docker Compose: ajouter service Meilisearch (1h)
 | |
| - [ ] Backend: script indexation `meilisearch-indexer.mjs` (3h)
 | |
| - [ ] Créer `SearchMeilisearchService` Angular (2h)
 | |
| - [ ] Mapper opérateurs Obsidian → filtres (3h)
 | |
| - [ ] Route `/api/search` avec parsing opérateurs (3h)
 | |
| - [ ] Tests: opérateurs `tag:`, `path:`, `file:` (2h)
 | |
| 
 | |
| **Critère de succès:** Search retourne <150ms P95 sur 1000 notes
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Jour 8: Observabilité
 | |
| **Objectif:** Diagnostics production  
 | |
| **Tasks:**
 | |
| - [ ] Créer route POST `/api/log` (2h)
 | |
| - [ ] Implémenter validation + sanitization logs (2h)
 | |
| - [ ] Rotation logs automatique (10MB max) (1h)
 | |
| - [ ] Tests: batch 50 événements <50ms (1h)
 | |
| 
 | |
| **Critère de succès:** Logs persistés avec corrélation sessionId
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 2. PLAN D'IMPLÉMENTATION PAR ÉTAPES
 | |
| 
 | |
| ### Phase 1: CRITIQUE (Semaine 1-2) — 8 jours
 | |
| **Focus:** Sécurité + Performance bloquante
 | |
| 
 | |
| | Item | Effort | Dépendances | Risque |
 | |
| |------|--------|-------------|--------|
 | |
| | DOMPurify sanitization | 1j | Aucune | Faible |
 | |
| | CDK Virtual Scroll | 2j | Aucune | Faible |
 | |
| | Debounce index rebuild | 3j | Aucune | Moyen |
 | |
| | Markdown Web Worker | 4j | Aucune | Moyen |
 | |
| | Lazy load Mermaid/MathJax | 2j | Aucune | Faible |
 | |
| | Meilisearch integration | 5j | Docker setup | Élevé |
 | |
| | /api/log backend | 3j | Aucune | Faible |
 | |
| 
 | |
| **Livrable:** Version 1.1.0 — "Performance & Security"  
 | |
| **Métriques cibles:** TTI <2.5s, Search P95 <150ms, 0 vulnérabilités XSS
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Phase 2: OPTIMISATION (Semaine 3-4) — 7 jours
 | |
| **Focus:** Caching + Infra
 | |
| 
 | |
| | Item | Effort | Dépendances | Risque |
 | |
| |------|--------|-------------|--------|
 | |
| | Service Worker + Workbox | 3j | Aucune | Moyen |
 | |
| | Budgets Lighthouse | 0.5j | Aucune | Faible |
 | |
| | Dockerfile multi-stage | 2j | Aucune | Faible |
 | |
| | Variables d'env (12-factor) | 1.5j | Aucune | Faible |
 | |
| | CSP headers + NGINX | 1.5j | Docker | Faible |
 | |
| | Throttle RAF canvas | 1j | Aucune | Faible |
 | |
| | Tests E2E étendus | 2.5j | Playwright | Moyen |
 | |
| 
 | |
| **Livrable:** Version 1.2.0 — "Infrastructure"  
 | |
| **Métriques cibles:** Offline support, Image <150MB, A+ Mozilla Observatory
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Phase 3: NICE-TO-HAVE (Semaine 5+) — 5 jours
 | |
| **Focus:** Code splitting + Optimisations avancées
 | |
| 
 | |
| | Item | Effort | Dépendances | Risque |
 | |
| |------|--------|-------------|--------|
 | |
| | Lazy routes Angular | 3j | Routing refactor | Moyen |
 | |
| | GraphData memoization | 1.5j | Aucune | Faible |
 | |
| | markdown-it-attrs whitelist | 0.5j | Aucune | Faible |
 | |
| | Progressive rendering | 2j | Aucune | Moyen |
 | |
| | IndexedDB cache | 3j | Dexie.js | Moyen |
 | |
| | OpenTelemetry (opt.) | 4j | Infra monitoring | Élevé |
 | |
| 
 | |
| **Livrable:** Version 1.3.0 — "Polish"  
 | |
| **Métriques cibles:** Initial bundle <800KB, Cache hit rate >80%
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 3. MÉTRIQUES À SUIVRE (Performance & Erreurs)
 | |
| 
 | |
| ### A) Métriques Performance (Lighthouse + Custom)
 | |
| 
 | |
| | Métrique | Actuel (estimé) | Cible Phase 1 | Cible Phase 2 | Cible Phase 3 | Seuil Alerte |
 | |
| |----------|-----------------|---------------|---------------|---------------|--------------|
 | |
| | **TTI (Time to Interactive)** | 4.2s | 2.5s | 2.0s | 1.5s | >3s (P95) |
 | |
| | **LCP (Largest Contentful Paint)** | 2.8s | 2.0s | 1.5s | 1.2s | >2.5s (P75) |
 | |
| | **FID (First Input Delay)** | 120ms | 80ms | 50ms | 30ms | >100ms (P95) |
 | |
| | **CLS (Cumulative Layout Shift)** | 0.15 | 0.1 | 0.05 | 0.02 | >0.1 (P75) |
 | |
| | **Bundle Size (initial)** | 2.8MB | 1.8MB | 1.5MB | 800KB | >2MB |
 | |
| | **Bundle Size (lazy chunks)** | N/A | 500KB | 300KB | 200KB | >500KB |
 | |
| | **Search P95 Latency** | 800ms | 150ms | 100ms | 50ms | >200ms |
 | |
| | **Graph Interaction P95** | 1500ms | 500ms | 100ms | 50ms | >300ms |
 | |
| | **Markdown Parse P95** | 500ms | 100ms | 50ms | 16ms | >150ms |
 | |
| | **Memory Heap (steady state)** | 120MB | 100MB | 80MB | 60MB | >150MB |
 | |
| 
 | |
| **Outils de mesure:**
 | |
| - Lighthouse CI (automatisé dans pipeline)
 | |
| - Chrome DevTools Performance profiler
 | |
| - `performance.mark()` + `performance.measure()` custom
 | |
| - Real User Monitoring (RUM) via `/api/log` PERFORMANCE_METRIC events
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### B) Métriques Erreurs & Stabilité
 | |
| 
 | |
| | Métrique | Cible | Seuil Alerte | Action |
 | |
| |----------|-------|--------------|--------|
 | |
| | **Error Rate** | <0.1% sessions | >1% | Rollback deploy |
 | |
| | **XSS Vulnerabilities** | 0 | >0 | Blocage release |
 | |
| | **Search Error Rate** | <0.5% queries | >2% | Investigate index corruption |
 | |
| | **Graph Freeze Rate** | <0.1% interactions | >1% | Degrade to simple view |
 | |
| | **Worker Crash Rate** | <0.01% | >0.5% | Fallback to sync mode |
 | |
| | **API /log Uptime** | >99.5% | <95% | Scale backend |
 | |
| | **CSP Violations** | <10/day | >100/day | Review inline scripts |
 | |
| 
 | |
| **Alertes configurées via:**
 | |
| - Sentry (erreurs runtime)
 | |
| - LogRocket (session replay on error)
 | |
| - Custom `/api/log` aggregation + Grafana
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### C) Métriques Business (UX)
 | |
| 
 | |
| | Métrique | Actuel | Cible | Mesure |
 | |
| |----------|--------|-------|--------|
 | |
| | **Searches per Session** | 2.3 | 4.0 | Via SEARCH_EXECUTED events |
 | |
| | **Graph View Engagement** | 15% users | 40% | Via GRAPH_VIEW_OPEN events |
 | |
| | **Bookmark Usage** | 8% users | 25% | Via BOOKMARKS_MODIFY events |
 | |
| | **Session Duration** | 3.2min | 8min | Via APP_START → APP_STOP |
 | |
| | **Bounce Rate (no interaction)** | 35% | <20% | First event within 30s |
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 4. COMMANDES À EXÉCUTER (Vérification & Bench)
 | |
| 
 | |
| ### Performance Benchmark
 | |
| 
 | |
| **Lighthouse CI (automatisé):**
 | |
| ```bash
 | |
| # Install
 | |
| npm install -g @lhci/cli
 | |
| 
 | |
| # Run Lighthouse on dev server
 | |
| ng serve &
 | |
| sleep 5
 | |
| lhci autorun --config=.lighthouserc.json
 | |
| 
 | |
| # Expected output:
 | |
| # ✅ TTI: <2.5s
 | |
| # ✅ FCP: <1.5s
 | |
| # ✅ Performance Score: >85
 | |
| ```
 | |
| 
 | |
| **`.lighthouserc.json`:**
 | |
| ```json
 | |
| {
 | |
|   "ci": {
 | |
|     "collect": {
 | |
|       "url": ["http://localhost:3000"],
 | |
|       "numberOfRuns": 3
 | |
|     },
 | |
|     "assert": {
 | |
|       "assertions": {
 | |
|         "categories:performance": ["error", {"minScore": 0.85}],
 | |
|         "first-contentful-paint": ["error", {"maxNumericValue": 1500}],
 | |
|         "interactive": ["error", {"maxNumericValue": 2500}],
 | |
|         "cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}]
 | |
|       }
 | |
|     },
 | |
|     "upload": {
 | |
|       "target": "temporary-public-storage"
 | |
|     }
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Bundle Analysis
 | |
| 
 | |
| ```bash
 | |
| # Build with stats
 | |
| ng build --configuration=production --stats-json
 | |
| 
 | |
| # Analyze with webpack-bundle-analyzer
 | |
| npx webpack-bundle-analyzer dist/stats.json
 | |
| 
 | |
| # Expected:
 | |
| # ✅ Initial bundle: <1.5MB
 | |
| # ✅ Vendor chunk: <800KB
 | |
| # ✅ Lazy chunks: <300KB each
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Search Performance Test
 | |
| 
 | |
| **Script: `scripts/bench-search.ts`**
 | |
| ```typescript
 | |
| import { performance } from 'perf_hooks';
 | |
| 
 | |
| async function benchSearch() {
 | |
|   const queries = [
 | |
|     'tag:#project',
 | |
|     'path:folder1/ important',
 | |
|     'file:home -tag:#archive',
 | |
|     'has:attachment task:TODO'
 | |
|   ];
 | |
|   
 | |
|   const results = [];
 | |
|   
 | |
|   for (const query of queries) {
 | |
|     const start = performance.now();
 | |
|     
 | |
|     const response = await fetch('http://localhost:4000/api/search', {
 | |
|       method: 'POST',
 | |
|       headers: { 'Content-Type': 'application/json' },
 | |
|       body: JSON.stringify({ query, vaultId: 'primary' })
 | |
|     });
 | |
|     
 | |
|     const data = await response.json();
 | |
|     const duration = performance.now() - start;
 | |
|     
 | |
|     results.push({
 | |
|       query,
 | |
|       duration,
 | |
|       hits: data.estimatedTotalHits,
 | |
|       serverTime: data.processingTimeMs
 | |
|     });
 | |
|   }
 | |
|   
 | |
|   console.table(results);
 | |
|   
 | |
|   const p95 = results.sort((a, b) => b.duration - a.duration)[Math.floor(results.length * 0.95)].duration;
 | |
|   console.log(`\n✅ Search P95: ${p95.toFixed(2)}ms (target: <150ms)`);
 | |
| }
 | |
| 
 | |
| benchSearch();
 | |
| ```
 | |
| 
 | |
| **Exécution:**
 | |
| ```bash
 | |
| npx ts-node scripts/bench-search.ts
 | |
| 
 | |
| # Expected output:
 | |
| # ┌─────────┬──────────────────────────────┬──────────┬──────┬────────────┐
 | |
| # │ (index) │ query                         │ duration │ hits │ serverTime │
 | |
| # ├─────────┼──────────────────────────────┼──────────┼──────┼────────────┤
 | |
| # │ 0       │ 'tag:#project'                │ 48.2     │ 23   │ 12.5       │
 | |
| # │ 1       │ 'path:folder1/ important'     │ 52.7     │ 8    │ 15.8       │
 | |
| # │ 2       │ 'file:home -tag:#archive'     │ 45.3     │ 1    │ 10.2       │
 | |
| # │ 3       │ 'has:attachment task:TODO'    │ 61.5     │ 5    │ 18.9       │
 | |
| # └─────────┴──────────────────────────────┴──────────┴──────┴────────────┘
 | |
| # ✅ Search P95: 61.5ms (target: <150ms)
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### E2E Tests Performance
 | |
| 
 | |
| **Playwright config additions:**
 | |
| ```typescript
 | |
| // playwright.config.ts
 | |
| export default defineConfig({
 | |
|   use: {
 | |
|     trace: 'retain-on-failure',
 | |
|     video: 'on-first-retry',
 | |
|   },
 | |
|   reporter: [
 | |
|     ['html'],
 | |
|     ['json', { outputFile: 'test-results/results.json' }]
 | |
|   ],
 | |
|   timeout: 30000,
 | |
|   expect: {
 | |
|     timeout: 5000
 | |
|   }
 | |
| });
 | |
| ```
 | |
| 
 | |
| **Run E2E with performance assertions:**
 | |
| ```bash
 | |
| npx playwright test --reporter=html
 | |
| 
 | |
| # Expected:
 | |
| # ✅ search-performance.spec.ts (4/4 passed)
 | |
| #    - Search 500 notes completes in <150ms
 | |
| #    - No main thread freeze >100ms
 | |
| #    - UI remains interactive during search
 | |
| #    - Virtual scroll renders without CLS
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Docker Image Size Verification
 | |
| 
 | |
| ```bash
 | |
| # Build optimized image
 | |
| docker build -f docker/Dockerfile -t obsiviewer:optimized .
 | |
| 
 | |
| # Check size
 | |
| docker images obsiviewer:optimized
 | |
| 
 | |
| # Expected:
 | |
| # REPOSITORY          TAG         SIZE
 | |
| # obsiviewer          optimized   145MB  (vs 450MB before)
 | |
| 
 | |
| # Verify healthcheck
 | |
| docker run -d -p 4000:4000 --name test obsiviewer:optimized
 | |
| sleep 10
 | |
| docker inspect --format='{{.State.Health.Status}}' test
 | |
| 
 | |
| # Expected: healthy
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Security Scan
 | |
| 
 | |
| ```bash
 | |
| # XSS payload tests
 | |
| npm run test:e2e -- e2e/security-xss.spec.ts
 | |
| 
 | |
| # CSP violations check
 | |
| curl -I http://localhost:4000 | grep -i "content-security-policy"
 | |
| 
 | |
| # Expected:
 | |
| # content-security-policy: default-src 'self'; script-src 'self' 'unsafe-eval'; ...
 | |
| 
 | |
| # npm audit
 | |
| npm audit --production
 | |
| 
 | |
| # Expected:
 | |
| # found 0 vulnerabilities
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ### Meilisearch Index Stats
 | |
| 
 | |
| ```bash
 | |
| # Check index health
 | |
| curl http://localhost:7700/indexes/vault_primary/stats \
 | |
|   -H "Authorization: Bearer masterKey"
 | |
| 
 | |
| # Expected response:
 | |
| {
 | |
|   "numberOfDocuments": 823,
 | |
|   "isIndexing": false,
 | |
|   "fieldDistribution": {
 | |
|     "title": 823,
 | |
|     "content": 823,
 | |
|     "tags": 645,
 | |
|     "path": 823
 | |
|   }
 | |
| }
 | |
| 
 | |
| # Test search latency
 | |
| curl -X POST http://localhost:7700/indexes/vault_primary/search \
 | |
|   -H "Authorization: Bearer masterKey" \
 | |
|   -H "Content-Type: application/json" \
 | |
|   -d '{"q":"project","limit":50}' \
 | |
|   -w "\nTime: %{time_total}s\n"
 | |
| 
 | |
| # Expected: Time: 0.035s (<50ms)
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 5. DASHBOARD MÉTRIQUES (Grafana/Custom)
 | |
| 
 | |
| **Panels recommandés:**
 | |
| 
 | |
| 1. **Search Performance**
 | |
|    - P50/P95/P99 latency (line chart)
 | |
|    - Error rate (gauge)
 | |
|    - Queries per minute (counter)
 | |
| 
 | |
| 2. **Graph Interactions**
 | |
|    - Freeze events count (bar chart)
 | |
|    - Node click → selection latency (histogram)
 | |
|    - Viewport FPS (line chart)
 | |
| 
 | |
| 3. **Frontend Vitals**
 | |
|    - LCP, FID, CLS (timeseries)
 | |
|    - Bundle size evolution (area chart)
 | |
|    - Memory heap (line chart)
 | |
| 
 | |
| 4. **Backend Health**
 | |
|    - /api/vault response time (line chart)
 | |
|    - Meilisearch indexing status (state timeline)
 | |
|    - Log ingestion rate (counter)
 | |
| 
 | |
| 5. **User Engagement**
 | |
|    - Active sessions (gauge)
 | |
|    - Feature adoption (pie chart: search/graph/bookmarks/calendar)
 | |
|    - Session duration distribution (histogram)
 | |
| 
 | |
| **Exemple config Prometheus + Grafana:**
 | |
| ```yaml
 | |
| # docker-compose.yml additions
 | |
| services:
 | |
|   prometheus:
 | |
|     image: prom/prometheus
 | |
|     volumes:
 | |
|       - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
 | |
|     ports:
 | |
|       - "9090:9090"
 | |
|   
 | |
|   grafana:
 | |
|     image: grafana/grafana
 | |
|     ports:
 | |
|       - "3001:3000"
 | |
|     environment:
 | |
|       - GF_SECURITY_ADMIN_PASSWORD=admin
 | |
|     volumes:
 | |
|       - ./monitoring/grafana-dashboards:/var/lib/grafana/dashboards
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 6. CRITÈRES DE SUCCÈS GLOBAUX
 | |
| 
 | |
| ### Phase 1 (Semaine 1-2) ✅
 | |
| - [ ] Lighthouse Performance Score: **>85**
 | |
| - [ ] Search P95: **<150ms** (1000 notes)
 | |
| - [ ] TTI: **<2.5s**
 | |
| - [ ] Aucune vulnérabilité XSS détectée
 | |
| - [ ] Main thread freeze: **<100ms** sur toutes interactions
 | |
| - [ ] `/api/log` opérationnel avec rotation
 | |
| 
 | |
| ### Phase 2 (Semaine 3-4) ✅
 | |
| - [ ] Lighthouse Performance Score: **>90**
 | |
| - [ ] Image Docker: **<150MB**
 | |
| - [ ] Offline support: app charge depuis cache
 | |
| - [ ] CSP headers configurés, score Mozilla Observatory: **A+**
 | |
| - [ ] Tests E2E coverage: **>60%**
 | |
| - [ ] Bundle budgets respectés (no warnings)
 | |
| 
 | |
| ### Phase 3 (Semaine 5+) ✅
 | |
| - [ ] Initial bundle: **<800KB**
 | |
| - [ ] Search P95: **<50ms**
 | |
| - [ ] Graph interaction P95: **<50ms**
 | |
| - [ ] Cache hit rate: **>80%**
 | |
| - [ ] Memory steady state: **<60MB**
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 7. COMMANDES QUOTIDIENNES (CI/CD)
 | |
| 
 | |
| **Pre-commit:**
 | |
| ```bash
 | |
| npm run lint
 | |
| npm run test:unit
 | |
| ```
 | |
| 
 | |
| **Pre-push:**
 | |
| ```bash
 | |
| npm run build
 | |
| npm run test:e2e
 | |
| ```
 | |
| 
 | |
| **CI Pipeline (GitHub Actions exemple):**
 | |
| ```yaml
 | |
| name: CI
 | |
| 
 | |
| on: [push, pull_request]
 | |
| 
 | |
| jobs:
 | |
|   test:
 | |
|     runs-on: ubuntu-latest
 | |
|     steps:
 | |
|       - uses: actions/checkout@v3
 | |
|       - uses: actions/setup-node@v3
 | |
|         with:
 | |
|           node-version: '20'
 | |
|       - run: npm ci
 | |
|       - run: npm run lint
 | |
|       - run: npm run test:unit
 | |
|       - run: npm run build
 | |
|       - run: npx lhci autorun
 | |
|       - run: npm run test:e2e
 | |
|       
 | |
|   security:
 | |
|     runs-on: ubuntu-latest
 | |
|     steps:
 | |
|       - uses: actions/checkout@v3
 | |
|       - run: npm audit --production
 | |
|       - run: npm run test:e2e -- e2e/security-xss.spec.ts
 | |
|       
 | |
|   docker:
 | |
|     runs-on: ubuntu-latest
 | |
|     steps:
 | |
|       - uses: actions/checkout@v3
 | |
|       - run: docker build -t obsiviewer:${{ github.sha }} .
 | |
|       - run: |
 | |
|           SIZE=$(docker images obsiviewer:${{ github.sha }} --format "{{.Size}}")
 | |
|           echo "Image size: $SIZE"
 | |
|           # Fail if >200MB
 | |
| ```
 | |
| 
 | |
| ---
 | |
| 
 | |
| **FIN DU PLAN D'EXÉCUTION**
 | |
| 
 | |
| Toutes les métriques, commandes et critères sont prêts à être appliqués immédiatement.
 |