@nodefony/frontend 10.0.0-alpha.5 → 10.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -282,7 +282,7 @@ Le `TemplateHelper` injecte automatiquement le preamble React Fast Refresh pour
282
282
 
283
283
  ### Vite démarre sur un autre port que `devPort`
284
284
 
285
- Le port configuré est pris. Le supervisor retry automatiquement sur `port+1`, `port+2` (option `portRetryAttempts`). Vérifie le port résolu via `svc.status().port`.
285
+ Le port configuré est pris. Le supervisor retry automatiquement sur `port+1`, `port+2` (option `portRetryAttempts`). Vérifie le port résolu via `svc.status().port` — il vaut `null` tant qu'aucun port n'a été résolu, jamais le port demandé : un port qu'on espère n'est pas un port qui sert.
286
286
 
287
287
  ### Le browser refuse le cert HTTPS de Vite (`https: true`)
288
288
 
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
package/dist/index.js CHANGED
@@ -10,8 +10,8 @@ import { ViteBuilder } from "./nodefony/src/builders/ViteBuilder.js";
10
10
  import { ViteConfigGenerator } from "./nodefony/service/ViteConfigGenerator.js";
11
11
  import { ViteProcessSupervisor } from "./nodefony/service/ViteProcessSupervisor.js";
12
12
  import { TemplateHelper } from "./nodefony/src/template/TemplateHelper.js";
13
- import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
14
- import __decorate from "./_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
13
+ import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateMetadata.js";
14
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
15
15
  import FrontendService_default from "./nodefony/service/FrontendService.js";
16
16
  import { buildFrontendStatus, createFrontendAdminApi } from "./nodefony/src/FrontendAdminApi.js";
17
17
  import FrontendBuild from "./nodefony/command/frontend-build.js";
@@ -5,9 +5,9 @@ import { allowedHostPatternForTemplate, detectRemoteDev, isLoopbackHostname, isV
5
5
  import { ViteProcessSupervisor } from "./ViteProcessSupervisor.js";
6
6
  import { TemplateHelper } from "../src/template/TemplateHelper.js";
7
7
  import { familyPortBlocks, familyPortPlan, isolationGroup } from "../src/isolationGroups.js";
8
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
9
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
10
- import { Module, Service, extend, injectable, stripTrailingSlashes } from "nodefony";
8
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateMetadata.js";
9
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
10
+ import { FRONTEND_CHOICES, Module, Service, extend, injectable, stripTrailingSlashes } from "nodefony";
11
11
  import path from "node:path";
12
12
  import fs from "node:fs";
13
13
  //#region nodefony/service/FrontendService.ts
@@ -102,7 +102,8 @@ let FrontendService = class FrontendService extends Service {
102
102
  const env = this.kernel?.environment;
103
103
  if (env === "development" && this.cfg.autoStartInDevelopment) {
104
104
  if (this.entries.length === 0) {
105
- this.log("no frontend entries declared Vite supervisor not started", "INFO");
105
+ const engines = FRONTEND_CHOICES.filter((c) => c !== "none").join("|");
106
+ this.log(`aucune interface web n'est déclarée — le serveur de développement Vite ne démarre pas. Pour en poser une : \`nodefony create front [nom] --frontend <${engines}>\``, "WARNING");
106
107
  return;
107
108
  }
108
109
  const names = this.entries.map((e) => e.entryName);
@@ -49,17 +49,38 @@ const DEFAULTS = {
49
49
  portRetryAttempts: 3
50
50
  };
51
51
  /**
52
- * Un texte (message d'erreur OU sortie brute de Vite) dénonce-t-il un port occupé ?
52
+ * Un texte (message d'erreur OU sortie brute de Vite) dénonce-t-il un conflit de
53
+ * port FATAL — c'est-à-dire un port que vite n'a PAS pu prendre ?
53
54
  *
54
55
  * **Source UNIQUE** de cette décision. Elle était dupliquée en deux regex qui ont
55
56
  * divergé : l'une cherchait `port X is in use`, alors que Vite écrit
56
57
  * `Port 5173 is ALREADY in use`. Résultat, le retry de port ne se déclenchait
57
58
  * jamais et la seconde app perdait tout son frontend — un conflit de port pourtant
58
- * parfaitement rattrapable. On tolère donc les deux formulations, et on ne
59
- * l'écrit qu'ici (deux implémentations d'une même règle = dérive garantie).
59
+ * parfaitement rattrapable. On ne l'écrit donc qu'ici (deux implémentations d'une
60
+ * même règle = dérive garantie).
61
+ *
62
+ * 🔴 **`already` est OBLIGATOIRE, et c'est tout le sujet.** Vite émet trois textes
63
+ * qui contiennent « in use », et un seul signifie qu'il a échoué — vérifié au
64
+ * SOURCE de `httpServerStart` (vite 8.3.0, `dist/node/chunks/node.js`) :
65
+ *
66
+ * - `Port X is already in use` → `throw` : vite MEURT. Seul cas rattrapable par
67
+ * un repli sur `port + 1`.
68
+ * - `Port X is in use, trying another one…` → `logger.info` : vite cherche
69
+ * lui-même le port suivant et annoncera son `Local:`. Rien à rattraper.
70
+ * - `Port X is in use on a wildcard address, but H:X is available…` →
71
+ * `logger.warn` sur un `listen` **RÉUSSI** : vite sert sur le port demandé.
72
+ *
73
+ * Rendre `already` optionnel confondait les trois. La conséquence ne se voyait
74
+ * pas sur un poste, où vite meurt vite : elle se voyait sur un agent partagé et
75
+ * lent, via `startupTimeoutMessage`. Un démarrage qui avait seulement AVERTI
76
+ * (wildcard) puis dépassé l'échéance sortait sous un libellé `EADDRINUSE:`, donc
77
+ * `spawnWithPortRetry` repliait — et chaque repli repayait l'échéance entière.
78
+ * Mesuré sur la forge : le cas d'intégration du décalage de port tué à 60 s
79
+ * (`4 tentatives × startupTimeoutMs`) quand les cinq autres du même fichier
80
+ * tenaient en 300 à 700 ms.
60
81
  */
61
82
  function isPortInUseMessage(text) {
62
- return /EADDRINUSE/i.test(text) || /address already in use/i.test(text) || /port\s+\d+\s+is\s+(?:already\s+)?in use/i.test(text);
83
+ return /EADDRINUSE/i.test(text) || /address already in use/i.test(text) || /port\s+\d+\s+is\s+already\s+in use/i.test(text);
63
84
  }
64
85
  /**
65
86
  * Message d'échec d'un boot qui n'a jamais rendu la main dans le temps imparti —
@@ -183,7 +204,7 @@ var ViteProcessSupervisor = class {
183
204
  state: this.state,
184
205
  host: this.opts.devHost,
185
206
  origin: this.resolvedOrigin,
186
- port: this.resolvedPort ?? this.opts.devPort,
207
+ port: this.resolvedPort,
187
208
  pid: this.child?.pid ?? null,
188
209
  lastError: this.lastError,
189
210
  entries: this.entries,
@@ -201,6 +222,7 @@ var ViteProcessSupervisor = class {
201
222
  const maxAttempts = this.cfg.portRetryAttempts;
202
223
  let lastErr = null;
203
224
  this.portRetries = 0;
225
+ this.resolvedPort = null;
204
226
  for (let i = 0; i <= maxAttempts; i++) {
205
227
  const port = this.opts.devPort + i;
206
228
  try {
@@ -475,7 +497,11 @@ var ViteProcessSupervisor = class {
475
497
  */
476
498
  pingVite() {
477
499
  return new Promise((resolve, reject) => {
478
- const port = this.resolvedPort ?? this.opts.devPort;
500
+ const port = this.resolvedPort;
501
+ if (port === null) {
502
+ reject(/* @__PURE__ */ new Error("aucun port résolu — rien à sonder"));
503
+ return;
504
+ }
479
505
  const req = (this.opts.https ? https : http).request({
480
506
  hostname: browserReachableHost(this.opts.devHost),
481
507
  port,
@@ -74,14 +74,35 @@ export interface ViteSupervisorOptions {
74
74
  readonly portRetryAttempts?: number;
75
75
  }
76
76
  /**
77
- * Un texte (message d'erreur OU sortie brute de Vite) dénonce-t-il un port occupé ?
77
+ * Un texte (message d'erreur OU sortie brute de Vite) dénonce-t-il un conflit de
78
+ * port FATAL — c'est-à-dire un port que vite n'a PAS pu prendre ?
78
79
  *
79
80
  * **Source UNIQUE** de cette décision. Elle était dupliquée en deux regex qui ont
80
81
  * divergé : l'une cherchait `port X is in use`, alors que Vite écrit
81
82
  * `Port 5173 is ALREADY in use`. Résultat, le retry de port ne se déclenchait
82
83
  * jamais et la seconde app perdait tout son frontend — un conflit de port pourtant
83
- * parfaitement rattrapable. On tolère donc les deux formulations, et on ne
84
- * l'écrit qu'ici (deux implémentations d'une même règle = dérive garantie).
84
+ * parfaitement rattrapable. On ne l'écrit donc qu'ici (deux implémentations d'une
85
+ * même règle = dérive garantie).
86
+ *
87
+ * 🔴 **`already` est OBLIGATOIRE, et c'est tout le sujet.** Vite émet trois textes
88
+ * qui contiennent « in use », et un seul signifie qu'il a échoué — vérifié au
89
+ * SOURCE de `httpServerStart` (vite 8.3.0, `dist/node/chunks/node.js`) :
90
+ *
91
+ * - `Port X is already in use` → `throw` : vite MEURT. Seul cas rattrapable par
92
+ * un repli sur `port + 1`.
93
+ * - `Port X is in use, trying another one…` → `logger.info` : vite cherche
94
+ * lui-même le port suivant et annoncera son `Local:`. Rien à rattraper.
95
+ * - `Port X is in use on a wildcard address, but H:X is available…` →
96
+ * `logger.warn` sur un `listen` **RÉUSSI** : vite sert sur le port demandé.
97
+ *
98
+ * Rendre `already` optionnel confondait les trois. La conséquence ne se voyait
99
+ * pas sur un poste, où vite meurt vite : elle se voyait sur un agent partagé et
100
+ * lent, via `startupTimeoutMessage`. Un démarrage qui avait seulement AVERTI
101
+ * (wildcard) puis dépassé l'échéance sortait sous un libellé `EADDRINUSE:`, donc
102
+ * `spawnWithPortRetry` repliait — et chaque repli repayait l'échéance entière.
103
+ * Mesuré sur la forge : le cas d'intégration du décalage de port tué à 60 s
104
+ * (`4 tentatives × startupTimeoutMs`) quand les cinq autres du même fichier
105
+ * tenaient en 300 à 700 ms.
85
106
  */
86
107
  export declare function isPortInUseMessage(text: string): boolean;
87
108
  /**
package/docs/index.md CHANGED
@@ -558,7 +558,7 @@ garde le port habituel (`PRIMARY_FAMILY`, `isolationGroups.ts:56`).
558
558
 
559
559
  **Les familles démarrent indépendamment.** Si Angular échoue, React continue de fonctionner : le
560
560
  démarrage n'échoue que si **aucune** famille n'a pu démarrer (`FrontendService.startDev()`,
561
- `FrontendService.ts:316`).
561
+ `FrontendService.ts:339`).
562
562
 
563
563
  ### Résilience — ce qui se passe quand Vite tombe
564
564
 
@@ -567,11 +567,11 @@ les processus meurent.
567
567
 
568
568
  | Situation | Réponse |
569
569
  | -------------------------- | ----------------------------------------------------------------------------------------------------- |
570
- | Port occupé au lancement | essai sur le port suivant, jusqu'à `portRetryAttempts` (`ViteProcessSupervisor.ts:276`) |
570
+ | Port occupé au lancement | essai sur le port suivant, jusqu'à `portRetryAttempts` (`ViteProcessSupervisor.ts:297`) |
571
571
  | Vite plante | relance avec délai exponentiel plafonné (`scheduleRestart()`, `ViteProcessSupervisor.ts:674`) |
572
572
  | Vite ne répond plus (gelé) | sonde périodique ; après N échecs, Vite est tué pour être relancé (`ViteProcessSupervisor.ts:599`) |
573
573
  | Deux `start()` concurrents | la promesse en cours est partagée — jamais deux processus |
574
- | Ctrl+C au terminal | le signal marque un arrêt **voulu** : pas de relance (`markShutdown`, `ViteProcessSupervisor.ts:245`) |
574
+ | Ctrl+C au terminal | le signal marque un arrêt **voulu** : pas de relance (`markShutdown`, `ViteProcessSupervisor.ts:266`) |
575
575
  | Arrêt du kernel | `SIGINT`, puis `SIGKILL` après 3 s — aucun zombie ne bloque le port (`ViteProcessSupervisor.ts:26`) |
576
576
 
577
577
  Deux subtilités valent d'être connues, parce qu'elles expliquent des comportements sinon
@@ -581,12 +581,16 @@ incompréhensibles :
581
581
  marquage du signal reçu par le processus serveur, un simple Ctrl+C ferait apparaître un
582
582
  « redémarrage échoué » en erreur, sur un arrêt parfaitement normal.
583
583
  - **La détection d'un port occupé est écrite à un seul endroit** (`isPortInUseMessage()`,
584
- `ViteProcessSupervisor.ts:164`), et tolère les deux formulations de Vite is in use » comme « is
585
- **already** in use »). Deux implémentations de la même règle avaient divergé : la reprise sur port
586
- ne se déclenchait jamais, et la seconde application perdait toute son interface.
584
+ `ViteProcessSupervisor.ts:185`), et elle reconnaît un conflit **FATAL**, pas la simple présence de
585
+ « in use ». Vite écrit trois textes qui contiennent ces mots, et un seul signifie qu'il a échoué :
586
+ « Port X is **already** in use » (il meurt le seul cas où reprendre sur un autre port a un sens),
587
+ « Port X is in use, **trying another one…** » (il se décale lui-même) et « Port X is in use **on a
588
+ wildcard address**, but H:X is available… » (il a pris le port demandé et se contente d'avertir).
589
+ Les confondre faisait reprendre sur un autre port un démarrage qui n'avait aucun conflit, en
590
+ repayant l'attente de démarrage à chaque essai.
587
591
 
588
592
  Les écouteurs attachés au processus enfant sont suivis puis retirés à chaque mort
589
- (`cleanupChildListeners()`, `ViteProcessSupervisor.ts:922`) : sans cela, les relances successives les
593
+ (`cleanupChildListeners()`, `ViteProcessSupervisor.ts:971`) : sans cela, les relances successives les
590
594
  accumuleraient jusqu'à l'avertissement de fuite.
591
595
 
592
596
  ## 🧰 API publique
@@ -685,7 +689,7 @@ Dans une application générée par `nodefony create app`, tu n'as pas à y pens
685
689
  **`npm run build` construit l'application entière** — le backend (rolldown) puis le front (il
686
690
  chaîne `nodefony frontend:build`). Un seul geste avant `npm start` ou dans un pipeline.
687
691
 
688
- `FrontendService.build()` (`FrontendService.ts:761`) appelle Vite **entrée par entrée**, et non une
692
+ `FrontendService.build()` (`FrontendService.ts:799`) appelle Vite **entrée par entrée**, et non une
689
693
  fois pour toutes. Ce n'est pas un détail : chaque bundle a sa racine, son dossier de sortie, sa base
690
694
  et son manifeste — c'est ce qui rend le multi-modules possible et ce qui isole Angular.
691
695
 
@@ -821,7 +825,7 @@ Sur le chemin chaud du rendu, trois précautions :
821
825
  - le **manifeste** est lu une fois par dossier de sortie, jamais par requête ;
822
826
  - l'**`index.html`** est mis en cache en production (relu en développement, où la fraîcheur prime) ;
823
827
  - les **écouteurs** du processus enfant sont suivis et retirés à chaque mort
824
- (`trackListener()`, `ViteProcessSupervisor.ts:912`) — sans quoi les relances les accumuleraient.
828
+ (`trackListener()`, `ViteProcessSupervisor.ts:961`) — sans quoi les relances les accumuleraient.
825
829
 
826
830
  La sonde de vie coûte une requête HTTP toutes les trente secondes par famille. Elle est désactivable
827
831
  (`healthCheckIntervalMs: 0`) si ce budget te gêne, au prix de la détection d'un Vite gelé.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/frontend",
3
- "version": "10.0.0-alpha.5",
3
+ "version": "10.0.0-alpha.7",
4
4
  "description": "Construction et rechargement à chaud des frontends de chaque module Nodefony — Vite intégré, multi-framework (React, Vue, Angular, Svelte)",
5
5
  "author": "Christophe CAMENSULI <ccamensuli@gmail.com>",
6
6
  "type": "module",
@@ -36,22 +36,22 @@
36
36
  "esm"
37
37
  ],
38
38
  "peerDependencies": {
39
- "@nodefony/framework": "^10.0.0-alpha.5",
40
- "@nodefony/http": "^10.0.0-alpha.5",
41
- "nodefony": "^10.0.0-alpha.5",
42
- "zod": "^4.6.1"
39
+ "@nodefony/framework": "^10.0.0-alpha.7",
40
+ "@nodefony/http": "^10.0.0-alpha.7",
41
+ "nodefony": "^10.0.0-alpha.7",
42
+ "zod": "^4.6.5"
43
43
  },
44
44
  "devDependencies": {
45
- "@nodefony/framework": "^10.0.0-alpha.5",
46
- "@nodefony/http": "^10.0.0-alpha.5",
45
+ "@nodefony/framework": "^10.0.0-alpha.7",
46
+ "@nodefony/http": "^10.0.0-alpha.7",
47
47
  "@types/chai": "5.2.3",
48
- "@types/node": "26.5.1",
49
- "@vitest/coverage-v8": "5.0.0",
48
+ "@types/node": "26.6.1",
49
+ "@vitest/coverage-v8": "5.0.1",
50
50
  "chai": "6.2.2",
51
- "nodefony": "^10.0.0-alpha.5",
51
+ "nodefony": "^10.0.0-alpha.7",
52
52
  "rimraf": "6.1.3",
53
53
  "vite": "8.3.0",
54
- "vitest": "5.0.0"
54
+ "vitest": "5.0.1"
55
55
  },
56
56
  "private": false,
57
57
  "engines": {