Stack qualité CI (MegaLinter, Trivy, ESLint) et purge du backlog lint web
Some checks failed
Qualité & Sécurité / MegaLinter (PMD · Ruff · Bandit · gitleaks · hadolint) (push) Successful in 1m9s
Build & Push Images / build (brain) (push) Successful in 1m10s
Qualité & Sécurité / Trivy (CVE dépendances Maven / pip / npm) (push) Failing after 2m36s
Build & Push Images / build (web) (push) Successful in 1m48s
Build & Push Images / build-switcher (push) Successful in 20s
Build & Push Images / build (core) (push) Successful in 3m9s
Qualité & Sécurité / Web (ESLint · angular-eslint + sonarjs) (push) Successful in 38s

CI (Gitea Actions) :
- Nouveau workflow quality.yml (séparé de ci.yml, non bloquant pour la release) :
  MegaLinter v9 (PMD, Ruff, Bandit, gitleaks, hadolint), ng lint (web) et
  Trivy (CVE des dépendances Maven/pip/npm), sur main + beta + PR
- Trivy : préchargement du cache Maven avant le scan — sans ça Maven Central
  rate-limite l'IP du runner (429) en résolvant les BOMs du parent Spring Boot
- upload-artifact v4 → v3 : l'API artifacts v4 n'est pas supportée par Gitea
  (corrige aussi l'upload du rapport Playwright de e2e.yml, cassé depuis toujours)
- Ruleset PMD projet (java-pmd-ruleset.xml, auto-détecté par MegaLinter) orienté
  bugs réels, sans le style Lombok-hostile du défaut : 11 337 → 65 findings ;
  PMD en mode rapport (JAVA_PMD_DISABLE_ERRORS) le temps de purger ce backlog

Web :
- angular-eslint 21 + eslint-plugin-sonarjs : les règles Sonar vivent désormais
  dans ESLint (config web/eslint.config.js, réglages justifiés en commentaire)
- tsconfig : skipLibCheck + types:[] + node_modules ré-exclu — répare les builds
  desktop Windows/Linux cassés par le conflit @types/eslint-scope vs ESLint 9
- Purge du backlog lint : 114 erreurs → 0 sur 66 fichiers (ngOnDestroy vides
  supprimés, any typés avec les vrais DTOs, outputs (close) → (closed), regex
  super-linéaires désamorcées, ternaires/fonctions imbriqués dépliés, intention
  documentée sur les catch/error volontairement vides)
- Bug latent corrigé au passage : license.service.disconnect() émettait
  undefined au lieu de true en cas de succès (masqué par un double cast)
This commit is contained in:
2026-07-07 01:05:00 +02:00
parent 3e9e828225
commit 42c4b53ced
71 changed files with 353 additions and 276 deletions

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError, of } from 'rxjs';
import { Observable, catchError, map, of } from 'rxjs';
/**
* Reflet de LicenseStatus (enum cote backend).
@@ -44,13 +44,13 @@ export interface BetaStatusDTO {
enabled: boolean;
updateAvailable: boolean;
anyUnknown: boolean;
images: Array<{
images: {
image: string;
localVersion: string | null;
remoteVersion: string | null;
status: 'UP_TO_DATE' | 'UPDATE_AVAILABLE' | 'UNKNOWN';
updateAvailable: boolean;
}>;
}[];
checkedAt: string;
disabledReason: string | null;
}
@@ -86,10 +86,13 @@ export class LicenseService {
disconnect(): Observable<boolean> {
return this.http.delete<void>(this.apiUrl, this.authOptions).pipe(
// Convertit en boolean : true = succes, false = erreur
// (catchError plus bas masque les detail HTTP)
catchError(() => of(false as any))
) as unknown as Observable<boolean>;
// Convertit en boolean : true = succes, false = erreur.
// (Avant : le cast `as unknown as` masquait l'absence de map() — le
// succes emettait `undefined`, donc falsy. Consommateur actuel ignore
// la valeur, mais le type est desormais honnete.)
map(() => true),
catchError(() => of(false))
);
}
refresh(): Observable<LicenseStatusDTO | null> {

View File

@@ -48,7 +48,9 @@ export interface NotebookAction {
resolution?: string;
}
const ACTION_RE = /```loremind-action\s*([\s\S]*?)```/g;
// Pas de \s* avant la capture (backtracking quadratique) : la capture est
// trim()ée avant JSON.parse de toute façon.
const ACTION_RE = /```loremind-action([\s\S]*?)```/g;
const VALID_TYPES = new Set(['npc', 'scene', 'chapter', 'arc', 'table']);
/**

View File

@@ -6,6 +6,13 @@ import { Notebook, NotebookArchive, NotebookDetail, NotebookSource, NotebookChat
import { LanguageService } from './language.service';
import { parseSseStream } from '../shared/sse.util';
/** Normalise les sources renvoyées par le Brain (événement SSE `sources`). */
function mapSseSources(sources: { source_id?: string; page?: number | null; score?: number }[] | undefined) {
return (sources ?? []).map(s => ({
sourceId: s.source_id ?? '', page: s.page ?? null, score: s.score ?? 0
}));
}
/**
* Service des notebooks (atelier RAG) : CRUD, upload/indexation de sources,
* et chat ancré STREAMÉ (SSE via fetch, comme ai-chat.service).
@@ -96,12 +103,7 @@ export class NotebookService {
} else if (event === 'sources') {
try {
const o = JSON.parse(data) as { sources?: { source_id?: string; page?: number | null; score?: number }[] };
emit({
type: 'sources',
sources: (o.sources ?? []).map(s => ({
sourceId: s.source_id ?? '', page: s.page ?? null, score: s.score ?? 0
}))
});
emit({ type: 'sources', sources: mapSseSources(o.sources) });
} catch { /* ignore */ }
} else if (event === 'progress') {
try {

View File

@@ -40,7 +40,7 @@ export interface Page {
* Pour chaque champ TABLE (colonnes figees au template, lignes libres) :
* fieldName → lignes ordonnees, chaque ligne = colonne → cellule.
*/
tableValues?: Record<string, Array<Record<string, string>>>;
tableValues?: Record<string, Record<string, string>[]>;
notes?: string | null;
tags?: string[];
relatedPageIds?: string[];

View File

@@ -46,9 +46,10 @@ export class VersionCheckerService {
*/
start(): void {
if (this.timer !== null) return;
void this.fetchInitial();
// Fire-and-forget : fetchInitial/poll gèrent leurs erreurs en interne.
this.fetchInitial();
this.timer = setInterval(
() => void this.poll(),
() => { this.poll(); },
VersionCheckerService.POLL_INTERVAL_MS,
);
}