@nodefony/frontend 10.0.0-alpha.1
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/LICENSE +544 -0
- package/README.md +338 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +63 -0
- package/dist/nodefony/command/frontend-build.js +68 -0
- package/dist/nodefony/command/frontend-dev.js +31 -0
- package/dist/nodefony/command/frontend-status.js +40 -0
- package/dist/nodefony/config/config.js +65 -0
- package/dist/nodefony/config/defineModuleConfig.js +34 -0
- package/dist/nodefony/interfaces/IFrontBuilder.js +1 -0
- package/dist/nodefony/interfaces/IFrontPreset.js +1 -0
- package/dist/nodefony/interfaces/IFrontendService.js +1 -0
- package/dist/nodefony/interfaces/IViteSupervisor.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/FrontendService.js +708 -0
- package/dist/nodefony/service/ViteConfigGenerator.js +139 -0
- package/dist/nodefony/service/ViteProcessSupervisor.js +589 -0
- package/dist/nodefony/src/FrontendAdminApi.js +113 -0
- package/dist/nodefony/src/builders/ViteBuilder.js +75 -0
- package/dist/nodefony/src/errors/FrontendError.js +50 -0
- package/dist/nodefony/src/isolationGroups.js +116 -0
- package/dist/nodefony/src/presets/angular-vite.js +27 -0
- package/dist/nodefony/src/presets/react19-vite.js +26 -0
- package/dist/nodefony/src/presets/svelte5-vite.js +37 -0
- package/dist/nodefony/src/presets/vanilla-vite.js +17 -0
- package/dist/nodefony/src/presets/vue3-vite.js +23 -0
- package/dist/nodefony/src/remoteDev.js +157 -0
- package/dist/nodefony/src/template/TemplateHelper.js +255 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/nodefony/command/frontend-build.d.ts +17 -0
- package/dist/types/nodefony/command/frontend-dev.d.ts +12 -0
- package/dist/types/nodefony/command/frontend-status.d.ts +14 -0
- package/dist/types/nodefony/config/config.d.ts +38 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +29 -0
- package/dist/types/nodefony/interfaces/IFrontBuilder.d.ts +69 -0
- package/dist/types/nodefony/interfaces/IFrontPreset.d.ts +26 -0
- package/dist/types/nodefony/interfaces/IFrontendService.d.ts +77 -0
- package/dist/types/nodefony/interfaces/IViteSupervisor.d.ts +56 -0
- package/dist/types/nodefony/interfaces/index.d.ts +4 -0
- package/dist/types/nodefony/service/FrontendService.d.ts +230 -0
- package/dist/types/nodefony/service/ViteConfigGenerator.d.ts +66 -0
- package/dist/types/nodefony/service/ViteProcessSupervisor.d.ts +214 -0
- package/dist/types/nodefony/src/FrontendAdminApi.d.ts +63 -0
- package/dist/types/nodefony/src/builders/ViteBuilder.d.ts +17 -0
- package/dist/types/nodefony/src/errors/FrontendError.d.ts +34 -0
- package/dist/types/nodefony/src/isolationGroups.d.ts +89 -0
- package/dist/types/nodefony/src/presets/angular-vite.d.ts +15 -0
- package/dist/types/nodefony/src/presets/react19-vite.d.ts +9 -0
- package/dist/types/nodefony/src/presets/svelte5-vite.d.ts +13 -0
- package/dist/types/nodefony/src/presets/vanilla-vite.d.ts +9 -0
- package/dist/types/nodefony/src/presets/vue3-vite.d.ts +11 -0
- package/dist/types/nodefony/src/remoteDev.d.ts +107 -0
- package/dist/types/nodefony/src/template/TemplateHelper.d.ts +99 -0
- package/docs/index.md +925 -0
- package/package.json +80 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { originWithHostname } from "../remoteDev.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { PLATFORM_EVENTS, escapeRegExp } from "nodefony";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
//#region nodefony/src/template/TemplateHelper.ts
|
|
7
|
+
/**
|
|
8
|
+
* Chemin résolu (1×, caché) du build navigateur de la debug bar Nodefony
|
|
9
|
+
* (`nodefony/debugbar`). `undefined` = pas encore tenté, `null` = irrésolu.
|
|
10
|
+
*/
|
|
11
|
+
let debugbarFile;
|
|
12
|
+
/** Marqueur optionnel dans l'`index.html` du module où injecter les tags. */
|
|
13
|
+
const FRONTEND_MARKER = "<!--nodefony:frontend-->";
|
|
14
|
+
/** Échappe une string pour usage littéral dans une RegExp. */
|
|
15
|
+
const escapeRe = escapeRegExp;
|
|
16
|
+
/**
|
|
17
|
+
* Génère les balises HTML à injecter dans la page rendue côté serveur
|
|
18
|
+
* pour brancher le frontend Vite.
|
|
19
|
+
*
|
|
20
|
+
* Dev : `<script type="module" src="http://host:port/@vite/client">` + entry.
|
|
21
|
+
* Prod : lira `manifest.json` du build pour les chemins fingerprintés (TODO).
|
|
22
|
+
*/
|
|
23
|
+
var TemplateHelper = class {
|
|
24
|
+
supervisor;
|
|
25
|
+
mode;
|
|
26
|
+
entries;
|
|
27
|
+
assetBaseUrl;
|
|
28
|
+
/**
|
|
29
|
+
* Manifests Vite parsés, cachés par `outDir` (lecture disque 1× par bundle).
|
|
30
|
+
* `null` = lecture tentée mais manifest absent/illisible (build manquant).
|
|
31
|
+
*/
|
|
32
|
+
manifestCache = /* @__PURE__ */ new Map();
|
|
33
|
+
/** `index.html` des modules, caché par `root` (prod only — dev re-lit). */
|
|
34
|
+
indexCache = /* @__PURE__ */ new Map();
|
|
35
|
+
/**
|
|
36
|
+
* @param supervisor superviseur Vite (dev) — `null` en prod (Vite ne tourne pas).
|
|
37
|
+
* @param mode bascule dev (URLs vers le dev server) / prod (manifest).
|
|
38
|
+
* @param entries entrées résolues — requises en prod pour `outDir`/`publicPath`.
|
|
39
|
+
* @param assetBaseUrl base CDN normalisée (sans slash final) préfixant les URLs
|
|
40
|
+
* prod émises ; `""` = origine Nodefony (chemins relatifs).
|
|
41
|
+
*/
|
|
42
|
+
constructor(supervisor, mode, entries = [], assetBaseUrl = "") {
|
|
43
|
+
this.supervisor = supervisor;
|
|
44
|
+
this.mode = mode;
|
|
45
|
+
this.entries = entries;
|
|
46
|
+
this.assetBaseUrl = assetBaseUrl;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Tags à injecter dans `<head>` (ou avant `</body>`) pour une entrée donnée.
|
|
50
|
+
* @param entryName nom logique de l'entrée (matche `entryName` dans IResolvedFrontendEntry)
|
|
51
|
+
* @param nonce nonce CSP de la requête (`Context.cspNonce`) — posé sur les `<script>`
|
|
52
|
+
* pour satisfaire `script-src 'nonce-…'` (preamble inline dev + entrée prod).
|
|
53
|
+
* @param requestHost nom d'hôte par lequel le client a demandé la PAGE
|
|
54
|
+
* (`Context.domain`, sans port). Dev : l'origine des assets est réécrite
|
|
55
|
+
* sur ce nom (scheme et port restent ceux de Vite) → poste et conteneur
|
|
56
|
+
* servis par la même instance. Prod : **ignoré** — les URLs du manifest
|
|
57
|
+
* sont relatives au document, elles suivent déjà l'hôte de la page.
|
|
58
|
+
*/
|
|
59
|
+
renderTags(entryName, nonce, requestHost) {
|
|
60
|
+
if (this.mode === "development") return this.renderDevTags(entryName, nonce, requestHost);
|
|
61
|
+
return this.renderProdTags(entryName, nonce);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Document HTML complet pour une entrée : lit l'`index.html` du module (le dev
|
|
65
|
+
* y met SES meta/polices/scripts externes), retire le `<script type=module>`
|
|
66
|
+
* de l'entrée source (Vite-native, non résolvable quand Nodefony sert la page)
|
|
67
|
+
* et injecte les tags Nodefony — au marqueur `<!--nodefony:frontend-->` sinon
|
|
68
|
+
* avant `</head>`. Pas d'`index.html` → coquille minimale générée.
|
|
69
|
+
*
|
|
70
|
+
* @param entryName nom logique de l'entrée
|
|
71
|
+
*/
|
|
72
|
+
renderDocument(entryName, nonce, requestHost) {
|
|
73
|
+
const tags = this.renderTags(entryName, nonce, requestHost);
|
|
74
|
+
const entry = this.entries.find((e) => e.entryName === entryName) ?? this.supervisor?.status().entries.find((e) => e.entryName === entryName);
|
|
75
|
+
const html = entry ? this.loadIndexHtml(entry.root) : null;
|
|
76
|
+
if (!html) return `<!DOCTYPE html>
|
|
77
|
+
<html lang="en">
|
|
78
|
+
<head>
|
|
79
|
+
<meta charset="utf-8" />
|
|
80
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
81
|
+
${tags}
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
<div id="root"></div>
|
|
85
|
+
</body>
|
|
86
|
+
</html>`;
|
|
87
|
+
return this.injectIntoHtml(html, tags, entry.entryFile);
|
|
88
|
+
}
|
|
89
|
+
/** Injecte `tags` dans `html` (marqueur > `</head>` > `</body>` > append). */
|
|
90
|
+
injectIntoHtml(html, tags, entryFile) {
|
|
91
|
+
const base = escapeRe(entryFile.replace(/\\/g, "/").split("/").pop() ?? "");
|
|
92
|
+
const stripped = base ? html.replace(new RegExp(`<script\\b[^>]*type=["']module["'][^>]*src=["'][^"']*${base}["'][^>]*>\\s*<\/script>`, "i"), "") : html;
|
|
93
|
+
if (stripped.includes(FRONTEND_MARKER)) return stripped.replace(FRONTEND_MARKER, tags);
|
|
94
|
+
if (/<\/head>/i.test(stripped)) return stripped.replace(/<\/head>/i, `${tags}\n</head>`);
|
|
95
|
+
if (/<\/body>/i.test(stripped)) return stripped.replace(/<\/body>/i, `${tags}\n</body>`);
|
|
96
|
+
return stripped + tags;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Lit `${root}/index.html`. Caché par root en **prod** (hot path) ; en dev,
|
|
100
|
+
* re-lu à chaque appel pour refléter les éditions du shell. `null` si absent.
|
|
101
|
+
*/
|
|
102
|
+
loadIndexHtml(root) {
|
|
103
|
+
if (this.mode === "production") {
|
|
104
|
+
const cached = this.indexCache.get(root);
|
|
105
|
+
if (cached !== void 0) return cached;
|
|
106
|
+
}
|
|
107
|
+
let html = null;
|
|
108
|
+
try {
|
|
109
|
+
html = fs.readFileSync(path.join(root, "index.html"), "utf8");
|
|
110
|
+
} catch {
|
|
111
|
+
html = null;
|
|
112
|
+
}
|
|
113
|
+
if (this.mode === "production") this.indexCache.set(root, html);
|
|
114
|
+
return html;
|
|
115
|
+
}
|
|
116
|
+
renderDevTags(entryName, nonce, requestHost) {
|
|
117
|
+
if (!this.supervisor) return `<!-- @nodefony/frontend: no vite supervisor (dev) -->`;
|
|
118
|
+
const status = this.supervisor.status();
|
|
119
|
+
if (status.state !== "ready") return `<!-- @nodefony/frontend: vite supervisor state=${status.state} -->`;
|
|
120
|
+
const entry = status.entries.find((e) => e.entryName === entryName);
|
|
121
|
+
if (!entry) return `<!-- @nodefony/frontend: unknown entry "${entryName}" -->`;
|
|
122
|
+
const resolvedOrigin = status.origin ?? `${status.https ? "https" : "http"}://${status.host}:${status.port}`;
|
|
123
|
+
const baseUrl = requestHost ? originWithHostname(resolvedOrigin, requestHost) ?? resolvedOrigin : resolvedOrigin;
|
|
124
|
+
const absEntryPath = path.resolve(entry.root, entry.entryFile).replace(/\\/g, "/");
|
|
125
|
+
const entryUrl = `${baseUrl}${absEntryPath.startsWith("/") ? `/@fs${absEntryPath}` : `/@fs/${absEntryPath}`}`;
|
|
126
|
+
const tags = [];
|
|
127
|
+
const n = nonce ? ` nonce="${nonce}"` : "";
|
|
128
|
+
if (entry.type === "react19") tags.push(`<script type="module"${n}>
|
|
129
|
+
import RefreshRuntime from "${baseUrl}/@react-refresh";
|
|
130
|
+
RefreshRuntime.injectIntoGlobalHook(window);
|
|
131
|
+
window.$RefreshReg$ = () => {};
|
|
132
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
133
|
+
window.__vite_plugin_react_preamble_installed__ = true;
|
|
134
|
+
<\/script>`);
|
|
135
|
+
tags.push(`<script type="module"${n} src="${baseUrl}/@vite/client"><\/script>`);
|
|
136
|
+
tags.push(`<script type="module"${n} src="${entryUrl}"><\/script>`);
|
|
137
|
+
tags.push(this.hmrBridgeTag(baseUrl, n));
|
|
138
|
+
const dbg = this.debugBarTag(baseUrl, entry.type, entryName, n);
|
|
139
|
+
if (dbg) tags.push(dbg);
|
|
140
|
+
return tags.join("\n");
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Pont HMR sans socket : relaie les événements globaux de Vite
|
|
144
|
+
* (`vite:afterUpdate`, etc.) vers un `CustomEvent` `nodefony:hmr` sur `window`,
|
|
145
|
+
* que la debug bar écoute. `createHotContext` réutilise le client HMR déjà
|
|
146
|
+
* ouvert par `@vite/client` — zéro connexion ajoutée. Tolérant aux versions
|
|
147
|
+
* (try/catch) : si l'export change, le compteur HMR reste à 0 sans casser la page.
|
|
148
|
+
*/
|
|
149
|
+
hmrBridgeTag(baseUrl, nonceAttr) {
|
|
150
|
+
return `<script type="module"${nonceAttr}>
|
|
151
|
+
try {
|
|
152
|
+
const { createHotContext } = await import("${baseUrl}/@vite/client");
|
|
153
|
+
const h = createHotContext("/@nodefony-debugbar-hmr");
|
|
154
|
+
const fire = (kind, path) =>
|
|
155
|
+
window.dispatchEvent(new CustomEvent("${PLATFORM_EVENTS.hmr}", { detail: { kind, path } }));
|
|
156
|
+
fire("connected");
|
|
157
|
+
h.on("vite:afterUpdate", (p) => {
|
|
158
|
+
const ups = (p && p.updates) || [];
|
|
159
|
+
if (ups.length) ups.forEach((u) => fire("update", u.acceptedPath || u.path));
|
|
160
|
+
else fire("update");
|
|
161
|
+
});
|
|
162
|
+
h.on("vite:beforeFullReload", () => fire("full-reload"));
|
|
163
|
+
h.on("vite:error", () => fire("error"));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
/* pont HMR indisponible — compteur debug bar à 0, page intacte */
|
|
166
|
+
}
|
|
167
|
+
<\/script>`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Tag d'injection de la debug bar (subpath `nodefony/debugbar`). Résout le
|
|
171
|
+
* fichier dist navigateur côté serveur (1×, caché) et le sert via `/@fs`.
|
|
172
|
+
* Renvoie un commentaire HTML (jamais d'erreur) si le subpath est irrésoluble.
|
|
173
|
+
*/
|
|
174
|
+
debugBarTag(baseUrl, framework, entryName, nonceAttr) {
|
|
175
|
+
if (debugbarFile === void 0) try {
|
|
176
|
+
debugbarFile = createRequire(import.meta.url).resolve("nodefony/debugbar");
|
|
177
|
+
} catch {
|
|
178
|
+
debugbarFile = null;
|
|
179
|
+
}
|
|
180
|
+
if (!debugbarFile) return `<!-- @nodefony/frontend: debugbar unresolved -->`;
|
|
181
|
+
const norm = debugbarFile.replace(/\\/g, "/");
|
|
182
|
+
const fsUrl = `${baseUrl}${norm.startsWith("/") ? `/@fs${norm}` : `/@fs/${norm}`}`;
|
|
183
|
+
const opts = JSON.stringify({ frontend: {
|
|
184
|
+
framework,
|
|
185
|
+
name: entryName,
|
|
186
|
+
viteOrigin: baseUrl
|
|
187
|
+
} });
|
|
188
|
+
return `<script type="module"${nonceAttr}>
|
|
189
|
+
import { mountDebugBar } from ${JSON.stringify(fsUrl)};
|
|
190
|
+
mountDebugBar(${opts});
|
|
191
|
+
<\/script>`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Prod : lit le `manifest.json` du build Vite (caché par `outDir`) et injecte
|
|
195
|
+
* les assets fingerprintés du chunk d'entrée — JS + CSS + preload des imports
|
|
196
|
+
* partagés — préfixés par le `publicPath` de l'entrée (servi par `Statics`).
|
|
197
|
+
*/
|
|
198
|
+
renderProdTags(entryName, nonce) {
|
|
199
|
+
const entry = this.entries.find((e) => e.entryName === entryName);
|
|
200
|
+
if (!entry) return `<!-- @nodefony/frontend: unknown entry "${entryName}" -->`;
|
|
201
|
+
const manifest = this.loadManifest(entry.outDir);
|
|
202
|
+
if (!manifest) return `<!-- @nodefony/frontend: prod manifest missing for "${entryName}" (run \`nodefony frontend:build\`) -->`;
|
|
203
|
+
const key = entry.entryFile.replace(/\\/g, "/");
|
|
204
|
+
const chunk = manifest[key] ?? Object.values(manifest).find((c) => c.isEntry);
|
|
205
|
+
if (!chunk) return `<!-- @nodefony/frontend: entry chunk "${key}" not in manifest -->`;
|
|
206
|
+
const base = this.assetBaseUrl + entry.publicPath;
|
|
207
|
+
const tags = [];
|
|
208
|
+
for (const href of this.collectCss(manifest, key)) tags.push(`<link rel="stylesheet" href="${base}${href}">`);
|
|
209
|
+
for (const imp of chunk.imports ?? []) {
|
|
210
|
+
const dep = manifest[imp];
|
|
211
|
+
if (dep) tags.push(`<link rel="modulepreload" href="${base}${dep.file}">`);
|
|
212
|
+
}
|
|
213
|
+
tags.push(`<script type="module"${nonce ? ` nonce="${nonce}"` : ""} crossorigin src="${base}${chunk.file}"><\/script>`);
|
|
214
|
+
return tags.join("\n");
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Lit + parse `${outDir}/.vite/manifest.json` (Vite ≥5) une seule fois par
|
|
218
|
+
* `outDir`. Retombe sur `${outDir}/manifest.json` (layout legacy).
|
|
219
|
+
*
|
|
220
|
+
* Un manifest TROUVÉ est caché (hot path : zéro disque par requête). Un
|
|
221
|
+
* manifest ABSENT n'est JAMAIS caché : figer l'absence condamnait le serveur
|
|
222
|
+
* à la page blanche jusqu'au restart, même après un `frontend:build` réussi
|
|
223
|
+
* (vécu). Le coût — 2 lectures ratées par rendu — n'existe que dans l'état
|
|
224
|
+
* dégradé « pas de build », qui n'est pas un hot path à défendre ; dès que
|
|
225
|
+
* le build apparaît, la lecture réussit et le cache reprend.
|
|
226
|
+
*/
|
|
227
|
+
loadManifest(outDir) {
|
|
228
|
+
const cached = this.manifestCache.get(outDir);
|
|
229
|
+
if (cached !== void 0) return cached;
|
|
230
|
+
let parsed = null;
|
|
231
|
+
for (const rel of [".vite/manifest.json", "manifest.json"]) try {
|
|
232
|
+
const raw = fs.readFileSync(path.join(outDir, rel), "utf8");
|
|
233
|
+
parsed = JSON.parse(raw);
|
|
234
|
+
break;
|
|
235
|
+
} catch {}
|
|
236
|
+
if (parsed !== null) this.manifestCache.set(outDir, parsed);
|
|
237
|
+
return parsed;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Collecte récursivement les fichiers CSS d'un chunk + de ses imports
|
|
241
|
+
* (le CSS d'un chunk partagé doit être chargé par toutes les entrées).
|
|
242
|
+
* Dédup via un `Set`, anti-cycle via l'ensemble des clés visitées.
|
|
243
|
+
*/
|
|
244
|
+
collectCss(manifest, key, seen = /* @__PURE__ */ new Set(), out = /* @__PURE__ */ new Set()) {
|
|
245
|
+
if (seen.has(key)) return out;
|
|
246
|
+
seen.add(key);
|
|
247
|
+
const chunk = manifest[key];
|
|
248
|
+
if (!chunk) return out;
|
|
249
|
+
for (const css of chunk.css ?? []) out.add(css);
|
|
250
|
+
for (const imp of chunk.imports ?? []) this.collectCss(manifest, imp, seen, out);
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
//#endregion
|
|
255
|
+
export { TemplateHelper, TemplateHelper as default };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Kernel, Module } from "nodefony";
|
|
2
|
+
import { type IFrontendConfigInput } from "./nodefony/config/defineModuleConfig.js";
|
|
3
|
+
import type { FrontendConfig } from "./nodefony/config/config.js";
|
|
4
|
+
import FrontendService from "./nodefony/service/FrontendService.js";
|
|
5
|
+
declare module "nodefony" {
|
|
6
|
+
interface NodefonyModuleConfig {
|
|
7
|
+
"@nodefony/frontend": IFrontendConfigInput;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
declare class Frontend extends Module<FrontendConfig> {
|
|
11
|
+
constructor(kernel: Kernel);
|
|
12
|
+
/** JSON Schema de la config frontend → data plane admin (config riche Studio). */
|
|
13
|
+
configSchema(): unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Phase `onRegister` : valide la config (défauts + override `module-frontend`)
|
|
16
|
+
* via `defineFrontendConfig`, puis la ré-assigne à `this.options` AVANT
|
|
17
|
+
* l'instanciation du `@services` (`FrontendService` lit `module.options` à sa
|
|
18
|
+
* construction). Plante propre avec messages clairs si la config est invalide
|
|
19
|
+
* (convention Zod figée 2026-05-28).
|
|
20
|
+
*/
|
|
21
|
+
onKernelRegister(): Promise<this>;
|
|
22
|
+
/**
|
|
23
|
+
* Phase `onBoot` : enregistre le producteur admin (`/nodefony/frontend/api/*`)
|
|
24
|
+
* auprès du broker, AVANT que framework ne monte le data plane à `onReady`.
|
|
25
|
+
* Handler lazy → le statut Vite est lu à la requête (superviseur démarré à
|
|
26
|
+
* `onServersReady`, bien après ce hook).
|
|
27
|
+
*/
|
|
28
|
+
onKernelBoot(): Promise<this>;
|
|
29
|
+
onKernelReady(): Promise<this>;
|
|
30
|
+
}
|
|
31
|
+
export default Frontend;
|
|
32
|
+
export { Frontend };
|
|
33
|
+
export { FrontendService };
|
|
34
|
+
export { createFrontendAdminApi, buildFrontendStatus, } from "./nodefony/src/FrontendAdminApi.js";
|
|
35
|
+
export type { IFrontendStatusView, IViteInstanceView, } from "./nodefony/src/FrontendAdminApi.js";
|
|
36
|
+
export { default as ViteBuilder } from "./nodefony/src/builders/ViteBuilder.js";
|
|
37
|
+
export { default as ViteProcessSupervisor } from "./nodefony/service/ViteProcessSupervisor.js";
|
|
38
|
+
export { default as ViteConfigGenerator } from "./nodefony/service/ViteConfigGenerator.js";
|
|
39
|
+
export { default as TemplateHelper } from "./nodefony/src/template/TemplateHelper.js";
|
|
40
|
+
export { default as react19Preset } from "./nodefony/src/presets/react19-vite.js";
|
|
41
|
+
export { default as vue3Preset } from "./nodefony/src/presets/vue3-vite.js";
|
|
42
|
+
export { default as angularPreset } from "./nodefony/src/presets/angular-vite.js";
|
|
43
|
+
export { default as vanillaPreset } from "./nodefony/src/presets/vanilla-vite.js";
|
|
44
|
+
export { default as svelte5Preset } from "./nodefony/src/presets/svelte5-vite.js";
|
|
45
|
+
export { FrontendError, FrontendPresetUnknownError, FrontendSupervisorStartError, FrontendNoEntriesError, } from "./nodefony/src/errors/FrontendError.js";
|
|
46
|
+
export type { IFrontPreset, FrontPresetType, } from "./nodefony/interfaces/IFrontPreset.js";
|
|
47
|
+
export type { IFrontBuilder, IFrontendModuleDeclaration, IResolvedFrontendEntry, } from "./nodefony/interfaces/IFrontBuilder.js";
|
|
48
|
+
export type { IViteSupervisor, IViteSupervisorStatus, ViteSupervisorState, } from "./nodefony/interfaces/IViteSupervisor.js";
|
|
49
|
+
export type { IFrontendService } from "./nodefony/interfaces/IFrontendService.js";
|
|
50
|
+
export { defineFrontendConfig, frontendConfigJsonSchema, type IFrontendConfigInput, } from "./nodefony/config/defineModuleConfig.js";
|
|
51
|
+
export { frontendConfigSchema, type FrontendConfig, } from "./nodefony/config/config.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CliKernel, Command } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* `nodefony frontend:build` — build production de tous les frontends déclarés.
|
|
4
|
+
*
|
|
5
|
+
* Écrit `public/dist/` + `manifest.json` par bundle (lu ensuite par
|
|
6
|
+
* `renderProdTags` + servi par `server-static`).
|
|
7
|
+
*
|
|
8
|
+
* - Idempotent : un bundle déjà à jour est **ignoré** (relance prod rapide).
|
|
9
|
+
* `--force` rebuild tout.
|
|
10
|
+
* - Erreurs : un bundle KO n'arrête pas les autres ; l'exit code passe à `1`
|
|
11
|
+
* s'il reste au moins un échec (cassure de pipeline CI).
|
|
12
|
+
*/
|
|
13
|
+
declare class FrontendBuild extends Command {
|
|
14
|
+
constructor(cli: CliKernel);
|
|
15
|
+
generate(): Promise<this>;
|
|
16
|
+
}
|
|
17
|
+
export default FrontendBuild;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CliKernel, Command } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* `nodefony frontend:dev` — démarre manuellement le superviseur Vite.
|
|
4
|
+
*
|
|
5
|
+
* Utile quand `autoStartInDevelopment: false` dans la config, ou pour
|
|
6
|
+
* relancer le superviseur Vite manuellement.
|
|
7
|
+
*/
|
|
8
|
+
declare class FrontendDev extends Command {
|
|
9
|
+
constructor(cli: CliKernel);
|
|
10
|
+
generate(): Promise<this>;
|
|
11
|
+
}
|
|
12
|
+
export default FrontendDev;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CliKernel, Command } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* `nodefony frontend:status` — lit l'état du superviseur Vite.
|
|
4
|
+
*
|
|
5
|
+
* Utilisé par Vision (Phase 10) pour afficher l'état du builder dans
|
|
6
|
+
* son tableau de bord admin.
|
|
7
|
+
*/
|
|
8
|
+
declare class FrontendStatus extends Command {
|
|
9
|
+
constructor(cli: CliKernel);
|
|
10
|
+
generate(_arg: string, opts: {
|
|
11
|
+
json: boolean;
|
|
12
|
+
}): Promise<this>;
|
|
13
|
+
}
|
|
14
|
+
export default FrontendStatus;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const frontendConfigSchema: z.ZodObject<{
|
|
3
|
+
devHost: z.ZodDefault<z.ZodString>;
|
|
4
|
+
devPort: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
publicOrigin: z.ZodDefault<z.ZodString>;
|
|
6
|
+
autoStartInDevelopment: z.ZodDefault<z.ZodBoolean>;
|
|
7
|
+
defaultOutDir: z.ZodDefault<z.ZodString>;
|
|
8
|
+
defaultRoot: z.ZodDefault<z.ZodString>;
|
|
9
|
+
assetBaseUrl: z.ZodDefault<z.ZodString>;
|
|
10
|
+
startupTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
pipeViteLogs: z.ZodDefault<z.ZodBoolean>;
|
|
12
|
+
backendHost: z.ZodDefault<z.ZodString>;
|
|
13
|
+
backendPort: z.ZodDefault<z.ZodNumber>;
|
|
14
|
+
backendProtocol: z.ZodDefault<z.ZodEnum<{
|
|
15
|
+
http: "http";
|
|
16
|
+
https: "https";
|
|
17
|
+
}>>;
|
|
18
|
+
https: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
viteEnv: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
20
|
+
resilience: z.ZodDefault<z.ZodObject<{
|
|
21
|
+
autoRestart: z.ZodDefault<z.ZodBoolean>;
|
|
22
|
+
maxRestarts: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
restartBackoffBaseMs: z.ZodDefault<z.ZodNumber>;
|
|
24
|
+
restartBackoffMaxMs: z.ZodDefault<z.ZodNumber>;
|
|
25
|
+
healthCheckIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
healthCheckFailureThreshold: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
healthCheckTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
28
|
+
portRetryAttempts: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
}, z.core.$strict>>;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
/** Type de sortie (config normalisée + défauts appliqués). */
|
|
32
|
+
export type FrontendConfig = z.infer<typeof frontendConfigSchema>;
|
|
33
|
+
/**
|
|
34
|
+
* Défauts du module, matérialisés depuis le schéma (source unique). Toujours
|
|
35
|
+
* valides par construction ; passés au `super(..., config)` du Module class.
|
|
36
|
+
*/
|
|
37
|
+
declare const config: FrontendConfig;
|
|
38
|
+
export default config;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { frontendConfigSchema, type FrontendConfig } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builder type-safe de la configuration de `@nodefony/frontend`.
|
|
5
|
+
*
|
|
6
|
+
* ⭐ TL;DR : MACHINERIE DE BOOT — on n'édite (presque) jamais ce fichier. Même
|
|
7
|
+
* pattern que `nodefony.config.ts` ↔ `defineConfig()` du core : `config.ts` PORTE
|
|
8
|
+
* la config (schéma + défauts), `define<X>Config()` la VALIDE au boot (parse +
|
|
9
|
+
* env + freeze) et publie le JSON Schema Studio.
|
|
10
|
+
*
|
|
11
|
+
* Principes (alignés sur `defineRealtimeConfig` / famille ORM) :
|
|
12
|
+
* - **Source unique** : `./config.ts` (Zod). Le builder VALIDE + GÈLE, ne dévie pas.
|
|
13
|
+
* - **Auto-documenté + introspectable** : chaque champ Zod porte `.describe()` →
|
|
14
|
+
* {@link frontendConfigJsonSchema} produit un JSON Schema que le panneau de
|
|
15
|
+
* config Studio consomme.
|
|
16
|
+
*
|
|
17
|
+
* @param config - configuration brute (sections omises = défauts sûrs).
|
|
18
|
+
* @returns config gelée prête pour `FrontendService`.
|
|
19
|
+
* @throws ZodError si invalide.
|
|
20
|
+
*/
|
|
21
|
+
export declare function defineFrontendConfig(config?: IFrontendConfigInput): FrontendConfig;
|
|
22
|
+
/**
|
|
23
|
+
* JSON Schema introspectable de la config frontend — destiné au panneau de config
|
|
24
|
+
* Studio (`/nodefony/config`).
|
|
25
|
+
*/
|
|
26
|
+
export declare function frontendConfigJsonSchema(): unknown;
|
|
27
|
+
/** Entrée du builder (champs avec défaut optionnels). */
|
|
28
|
+
export type IFrontendConfigInput = z.input<typeof frontendConfigSchema>;
|
|
29
|
+
export type { FrontendConfig };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { IFrontPreset } from "./IFrontPreset.js";
|
|
2
|
+
/**
|
|
3
|
+
* Déclaration frontend portée par un module consommateur dans son `config.ts` :
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* { frontend: { type: "react19", entry: "./frontend/src/main.tsx" } }
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export interface IFrontendModuleDeclaration {
|
|
10
|
+
/** Type de preset (résolu vers un IFrontPreset). */
|
|
11
|
+
readonly type: IFrontPreset["type"];
|
|
12
|
+
/** Entrée source relative à la racine du module (ex: "./frontend/src/main.tsx"). */
|
|
13
|
+
readonly entry: string;
|
|
14
|
+
/** Dossier de sortie de la prod build (relatif au module, défaut "./public/dist"). */
|
|
15
|
+
readonly outDir?: string;
|
|
16
|
+
/** Racine front (contient index.html), défaut "./frontend". */
|
|
17
|
+
readonly root?: string;
|
|
18
|
+
/** Nom logique de l'entrée multi-bundle (défaut = nom du module). */
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Préfixe public sous lequel les assets buildés sont servis en prod
|
|
22
|
+
* (par le serveur statique `Statics` de @nodefony/http, ou par un proxy
|
|
23
|
+
* frontal en cloud-native). Défaut `/_assets/<name>/`. Sert de `base` Vite
|
|
24
|
+
* au build ET de mount prefix au serveur statique → les deux restent alignés.
|
|
25
|
+
*/
|
|
26
|
+
readonly publicPath?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Préfixes de paths à proxifier depuis Vite vers Nodefony (dev only).
|
|
29
|
+
* Sans ça, un `fetch("/poc/api/data")` depuis l'app React servie par Vite
|
|
30
|
+
* tape Vite (qui retourne son index.html SPA-fallback) au lieu du backend.
|
|
31
|
+
* Exemple : `["/poc/api", "/nodefony"]`.
|
|
32
|
+
*/
|
|
33
|
+
readonly apiProxyPaths?: ReadonlyArray<string>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Description résolue d'une entrée front, prête à être passée au superviseur Vite.
|
|
37
|
+
*/
|
|
38
|
+
export interface IResolvedFrontendEntry {
|
|
39
|
+
readonly moduleName: string;
|
|
40
|
+
readonly entryName: string;
|
|
41
|
+
readonly type: IFrontPreset["type"];
|
|
42
|
+
readonly root: string;
|
|
43
|
+
readonly entryFile: string;
|
|
44
|
+
readonly outDir: string;
|
|
45
|
+
/**
|
|
46
|
+
* Préfixe public normalisé (leading + trailing `/`, ex `/_assets/studio/`)
|
|
47
|
+
* — `base` Vite en prod + mount prefix du serveur statique.
|
|
48
|
+
*/
|
|
49
|
+
readonly publicPath: string;
|
|
50
|
+
/** Préfixes à proxifier vers Nodefony (résolus depuis la déclaration). */
|
|
51
|
+
readonly apiProxyPaths: ReadonlyArray<string>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builder responsable de construire la config Vite finale à partir
|
|
55
|
+
* des déclarations modules + des presets.
|
|
56
|
+
*/
|
|
57
|
+
export interface IFrontBuilder {
|
|
58
|
+
/** Liste tous les presets enregistrés. */
|
|
59
|
+
listPresets(): ReadonlyArray<IFrontPreset>;
|
|
60
|
+
/** Récupère un preset par type. */
|
|
61
|
+
getPreset(type: IFrontPreset["type"]): IFrontPreset | undefined;
|
|
62
|
+
/** Enregistre / remplace un preset. */
|
|
63
|
+
registerPreset(preset: IFrontPreset): void;
|
|
64
|
+
/**
|
|
65
|
+
* Construit la config Vite (objet brut) pour une liste d'entrées résolues.
|
|
66
|
+
* Le builder lui-même n'instancie pas Vite — il fournit la config.
|
|
67
|
+
*/
|
|
68
|
+
buildViteConfig(entries: ReadonlyArray<IResolvedFrontendEntry>, mode: "development" | "production"): Promise<Record<string, unknown>>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset descriptor — applique une stack frontend (React, Vue, Angular, Svelte, vanilla).
|
|
3
|
+
*
|
|
4
|
+
* Un preset enrichit la config Vite avec les plugins, les extensions de fichiers
|
|
5
|
+
* et les dépendances pré-bundlées propres au framework cible.
|
|
6
|
+
*
|
|
7
|
+
* Cette union ne liste QUE les presets réellement enregistrés par le builder
|
|
8
|
+
* (`ViteBuilder`). Une valeur annoncée ici sans preset correspondant serait
|
|
9
|
+
* acceptée par le compilateur puis rejetée au démarrage
|
|
10
|
+
* (`FrontendPresetUnknownError`) — un refus qui doit tomber à la compilation.
|
|
11
|
+
*/
|
|
12
|
+
export type FrontPresetType = "react19" | "vue3" | "angular" | "svelte5" | "vanilla";
|
|
13
|
+
export interface IFrontPreset {
|
|
14
|
+
/** Identifiant unique du preset (ex: "react19"). */
|
|
15
|
+
readonly type: FrontPresetType;
|
|
16
|
+
/** Extensions sources reconnues (".tsx", ".vue", ".svelte"…). */
|
|
17
|
+
readonly extensions: ReadonlyArray<string>;
|
|
18
|
+
/** Dépendances à pré-scanner via `optimizeDeps.include`. */
|
|
19
|
+
readonly optimizeDepsInclude: ReadonlyArray<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Construit la liste des plugins Vite à injecter.
|
|
22
|
+
* Retour `unknown[]` pour éviter une dépendance dure sur les types Vite —
|
|
23
|
+
* les plugins sont résolus dynamiquement par le builder.
|
|
24
|
+
*/
|
|
25
|
+
buildPlugins(): Promise<unknown[]>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { IResolvedFrontendEntry } from "./IFrontBuilder.js";
|
|
2
|
+
import type { IViteSupervisorStatus } from "./IViteSupervisor.js";
|
|
3
|
+
/** Résultat d'un `build()` — exploité par la commande CLI (exit code pipeline). */
|
|
4
|
+
export interface IFrontendBuildResult {
|
|
5
|
+
/** Bundles effectivement (re)buildés. */
|
|
6
|
+
built: string[];
|
|
7
|
+
/** Bundles ignorés car déjà à jour (manifest plus récent que les sources). */
|
|
8
|
+
skipped: string[];
|
|
9
|
+
/** Bundles en échec (`entryName` + message Vite). */
|
|
10
|
+
failures: {
|
|
11
|
+
entryName: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* API publique du `FrontendService` injectable.
|
|
17
|
+
*
|
|
18
|
+
* Cycle de vie :
|
|
19
|
+
* 1. `onKernelReady` → scan modules → collecte entries
|
|
20
|
+
* 2. dev/start → délègue au superviseur Vite
|
|
21
|
+
* 3. prod/build → appelle le builder Vite (compile, manifest.json)
|
|
22
|
+
* 4. `onTerminate` → stop superviseur
|
|
23
|
+
*/
|
|
24
|
+
export interface IFrontendService {
|
|
25
|
+
/** Entrées front résolues (snapshot lecture). */
|
|
26
|
+
listEntries(): ReadonlyArray<IResolvedFrontendEntry>;
|
|
27
|
+
/**
|
|
28
|
+
* État de l'instance Vite primaire (`default`), pour compat. En multi-instance,
|
|
29
|
+
* préférer `statusAll()` pour voir chaque famille.
|
|
30
|
+
*/
|
|
31
|
+
status(): IViteSupervisorStatus;
|
|
32
|
+
/**
|
|
33
|
+
* État de **chaque** instance Vite, étiqueté par famille d'isolation
|
|
34
|
+
* (`default`, `angular`, …). Vide tant qu'aucune instance n'est démarrée.
|
|
35
|
+
*/
|
|
36
|
+
statusAll(): ReadonlyArray<{
|
|
37
|
+
family: string;
|
|
38
|
+
status: IViteSupervisorStatus;
|
|
39
|
+
}>;
|
|
40
|
+
/** Lance le dev server (idempotent — premier appel boot). */
|
|
41
|
+
startDev(): Promise<void>;
|
|
42
|
+
/** Stoppe proprement le superviseur. */
|
|
43
|
+
stopDev(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Build production — `vite.build()` par entry (manifest.json par bundle).
|
|
46
|
+
* @param opts.force rebuild même si le manifest est plus récent que les sources.
|
|
47
|
+
*/
|
|
48
|
+
build(opts?: {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
}): Promise<IFrontendBuildResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Helper template — retourne les balises `<script>` à injecter dans une page.
|
|
53
|
+
*
|
|
54
|
+
* @param nonce nonce CSP de la requête (`Context.cspNonce`).
|
|
55
|
+
* @param requestHost nom d'hôte par lequel le client a demandé la page
|
|
56
|
+
* (`Context.domain`, sans port). En développement, l'origine des assets
|
|
57
|
+
* Vite est dérivée de ce nom — le scheme et le port restent ceux de Vite —
|
|
58
|
+
* de sorte qu'un poste et un conteneur soient servis en même temps sans
|
|
59
|
+
* configuration. Ignoré si `frontend.publicOrigin` est configurée, si
|
|
60
|
+
* l'hôte ne franchit pas `trustedHosts`, et en production.
|
|
61
|
+
*/
|
|
62
|
+
renderTags(entryName: string, nonce?: string, requestHost?: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Document HTML complet : `index.html` du module + tags injectés. Pour les
|
|
65
|
+
* controllers qui veulent déléguer toute la coquille (le dev contrôle le
|
|
66
|
+
* `<head>` via son `index.html`).
|
|
67
|
+
*
|
|
68
|
+
* @param requestHost cf {@link IFrontendService.renderTags}.
|
|
69
|
+
*/
|
|
70
|
+
renderDocument(entryName: string, nonce?: string, requestHost?: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Résout l'URL publique d'un asset : préfixe `p` par `assetBaseUrl` (CDN) si
|
|
73
|
+
* configuré, sinon chemin relatif inchangé. URLs absolues renvoyées telles
|
|
74
|
+
* quelles. Helper template `asset('/x')`.
|
|
75
|
+
*/
|
|
76
|
+
assetUrl(p: string): string;
|
|
77
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { IResolvedFrontendEntry } from "./IFrontBuilder.js";
|
|
2
|
+
/**
|
|
3
|
+
* État courant du superviseur Vite — exposé pour observabilité (Vision, CLI).
|
|
4
|
+
*/
|
|
5
|
+
export type ViteSupervisorState = "idle" | "starting" | "ready" | "compiling" | "restarting" | "crashed" | "stopping" | "stopped" | "errored";
|
|
6
|
+
/**
|
|
7
|
+
* Snapshot lisible du superviseur, lu par TemplateHelper pour injecter
|
|
8
|
+
* les bons scripts (URL `host:port` Vite côté navigateur).
|
|
9
|
+
*/
|
|
10
|
+
export interface IViteSupervisorStatus {
|
|
11
|
+
readonly state: ViteSupervisorState;
|
|
12
|
+
readonly host: string;
|
|
13
|
+
readonly port: number | null;
|
|
14
|
+
/**
|
|
15
|
+
* Origine PUBLIQUE effective du dev server — celle que le navigateur doit
|
|
16
|
+
* utiliser (`publicOrigin` config si posée, sinon dérivée de `host:port`).
|
|
17
|
+
* Source unique des URLs émises (TemplateHelper, boot line, CSP, admin API) :
|
|
18
|
+
* un `scheme://host:port` recomposé ailleurs finirait par diverger.
|
|
19
|
+
* `null` tant qu'aucun spawn n'a résolu de port.
|
|
20
|
+
*/
|
|
21
|
+
readonly origin: string | null;
|
|
22
|
+
readonly pid: number | null;
|
|
23
|
+
readonly lastError: string | null;
|
|
24
|
+
readonly entries: ReadonlyArray<IResolvedFrontendEntry>;
|
|
25
|
+
/** Vite sert-il en HTTPS ? Utilisé par TemplateHelper pour préfixer les `<script>`. */
|
|
26
|
+
readonly https: boolean;
|
|
27
|
+
/** Nombre de redémarrages auto effectués depuis le premier `start()`. */
|
|
28
|
+
readonly restartCount: number;
|
|
29
|
+
/** Nombre d'échecs consécutifs du health check (reset à chaque succès). */
|
|
30
|
+
readonly healthFailures: number;
|
|
31
|
+
/**
|
|
32
|
+
* Nombre de REPLIS de port effectués au dernier démarrage — `0` quand Vite a
|
|
33
|
+
* pris le port demandé du premier coup.
|
|
34
|
+
*
|
|
35
|
+
* 🔴 Il est exposé pour rendre une cause OBSERVABLE, pas par curiosité. Quand
|
|
36
|
+
* une seconde application annonce le port de la première, deux explications
|
|
37
|
+
* s'affrontent et le port seul ne tranche pas : soit Vite n'a jamais dénoncé
|
|
38
|
+
* le conflit sous une forme reconnue et le repli n'est jamais parti
|
|
39
|
+
* (`portRetries === 0`), soit le repli a bien eu lieu et Vite a malgré tout
|
|
40
|
+
* fini sur le même numéro (`portRetries > 0`) — ce que `strictPort` doit
|
|
41
|
+
* interdire. Sans ce compteur, un banc rouge n'accuse personne et se relance.
|
|
42
|
+
*/
|
|
43
|
+
readonly portRetries: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Contrat du superviseur Vite — implémenté différemment selon la branche POC :
|
|
47
|
+
* - `poc/frontend-child` : ViteProcessSupervisor (child_process.spawn)
|
|
48
|
+
* - `poc/frontend-single` : ViteInProcSupervisor (vite.createServer() in-proc)
|
|
49
|
+
*
|
|
50
|
+
* L'API publique est la même, c'est le seul point d'isolement.
|
|
51
|
+
*/
|
|
52
|
+
export interface IViteSupervisor {
|
|
53
|
+
start(entries: ReadonlyArray<IResolvedFrontendEntry>, viteConfig: Record<string, unknown>): Promise<void>;
|
|
54
|
+
stop(): Promise<void>;
|
|
55
|
+
status(): IViteSupervisorStatus;
|
|
56
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { IFrontPreset, FrontPresetType } from "./IFrontPreset.js";
|
|
2
|
+
export type { IFrontBuilder, IFrontendModuleDeclaration, IResolvedFrontendEntry, } from "./IFrontBuilder.js";
|
|
3
|
+
export type { IViteSupervisor, IViteSupervisorStatus, ViteSupervisorState, } from "./IViteSupervisor.js";
|
|
4
|
+
export type { IFrontendService } from "./IFrontendService.js";
|