odoro 0.1.0

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.
Files changed (59) hide show
  1. package/LICENSE +12 -0
  2. package/client.d.ts +84 -0
  3. package/dist/build-SNTGJH2J.js +4 -0
  4. package/dist/chunk-2YDI5NKV.js +55 -0
  5. package/dist/chunk-G2WW7N4C.js +1872 -0
  6. package/dist/chunk-G5QXVBYT.js +1139 -0
  7. package/dist/chunk-JMEHF3KN.js +35 -0
  8. package/dist/chunk-PEMYUK2D.js +64 -0
  9. package/dist/chunk-T42X2NJN.js +98 -0
  10. package/dist/chunk-T6RLHSCW.js +123 -0
  11. package/dist/chunk-UGRSODHU.js +154 -0
  12. package/dist/cli.d.ts +40 -0
  13. package/dist/cli.js +236 -0
  14. package/dist/commands-FVAHVVVN.js +793 -0
  15. package/dist/commands-V44Y5G4F.js +245 -0
  16. package/dist/create-SH2M722Y.js +288 -0
  17. package/dist/index.d.ts +354 -0
  18. package/dist/index.js +7 -0
  19. package/dist/package-AUINPBEX.js +62 -0
  20. package/dist/preview-LOAO5Y6V.js +3 -0
  21. package/dist/registry/index.d.ts +316 -0
  22. package/dist/registry/index.js +2 -0
  23. package/dist/server-MZ76LPAG.js +3 -0
  24. package/package.json +57 -0
  25. package/templates/react-ts/README.md +52 -0
  26. package/templates/react-ts/_gitignore +8 -0
  27. package/templates/react-ts/index.html +13 -0
  28. package/templates/react-ts/odoro.config.ts +10 -0
  29. package/templates/react-ts/package.json +23 -0
  30. package/templates/react-ts/public/favicon.svg +4 -0
  31. package/templates/react-ts/src/App.tsx +66 -0
  32. package/templates/react-ts/src/main.tsx +18 -0
  33. package/templates/react-ts/src/odoro-env.d.ts +1 -0
  34. package/templates/react-ts/src/routes/About.tsx +19 -0
  35. package/templates/react-ts/src/routes/Home.tsx +80 -0
  36. package/templates/react-ts/src/routes/NotFound.tsx +14 -0
  37. package/templates/react-ts/src/styles.css +13 -0
  38. package/templates/react-ts/tsconfig.json +25 -0
  39. package/templates/react-ts-server/Dockerfile +51 -0
  40. package/templates/react-ts-server/README.md +87 -0
  41. package/templates/react-ts-server/_dockerignore +8 -0
  42. package/templates/react-ts-server/_env.example +78 -0
  43. package/templates/react-ts-server/_gitignore +8 -0
  44. package/templates/react-ts-server/client/index.html +13 -0
  45. package/templates/react-ts-server/client/public/favicon.svg +4 -0
  46. package/templates/react-ts-server/client/src/App.tsx +66 -0
  47. package/templates/react-ts-server/client/src/main.tsx +18 -0
  48. package/templates/react-ts-server/client/src/odoro-env.d.ts +1 -0
  49. package/templates/react-ts-server/client/src/routes/About.tsx +19 -0
  50. package/templates/react-ts-server/client/src/routes/Home.tsx +148 -0
  51. package/templates/react-ts-server/client/src/routes/NotFound.tsx +14 -0
  52. package/templates/react-ts-server/client/src/styles.css +13 -0
  53. package/templates/react-ts-server/odoro.config.ts +21 -0
  54. package/templates/react-ts-server/package.json +33 -0
  55. package/templates/react-ts-server/scripts/dev.mjs +74 -0
  56. package/templates/react-ts-server/server/src/main.ts +131 -0
  57. package/templates/react-ts-server/server/src/modules/health/index.ts +144 -0
  58. package/templates/react-ts-server/server/tsconfig.json +23 -0
  59. package/templates/react-ts-server/tsconfig.json +27 -0
@@ -0,0 +1,1139 @@
1
+ #!/usr/bin/env node
2
+ import { info, error, success, duration, colors, warn } from './chunk-JMEHF3KN.js';
3
+ import { existsSync, readFileSync, statSync, watch, createReadStream } from 'fs';
4
+ import { readFile, rm, mkdir, writeFile } from 'fs/promises';
5
+ import { createServer, request } from 'http';
6
+ import { resolve, join, dirname, extname, relative } from 'path';
7
+ import { createHash } from 'crypto';
8
+ import { createRequire } from 'module';
9
+ import { fileURLToPath, pathToFileURL } from 'url';
10
+ import { build } from 'esbuild';
11
+ import { transformAsync } from '@babel/core';
12
+ import reactRefreshPlugin from 'react-refresh/babel';
13
+
14
+ // src/dev/client.ts
15
+ var HMR_STREAM_PATH = "/@odoro/hmr";
16
+ var HMR_CLIENT_PATH = "/@odoro/client";
17
+ var HMR_CLIENT_SOURCE = String.raw`
18
+ const OVERLAY_ID = 'odoro-error-overlay'
19
+
20
+ /** Contextes de rechargement, par URL de module. */
21
+ const contexts = new Map()
22
+
23
+ /**
24
+ * Cree le contexte expose a un module via import.meta.hot.
25
+ * @param {string} url
26
+ */
27
+ export function createHotContext(url) {
28
+ const existing = contexts.get(url)
29
+ if (existing) {
30
+ // Rechargement du meme module : les rappels de la version precedente sont
31
+ // executes puis oublies.
32
+ for (const callback of existing.disposers) {
33
+ try {
34
+ callback(existing.data)
35
+ } catch (cause) {
36
+ console.error('[odoro] echec du nettoyage de', url, cause)
37
+ }
38
+ }
39
+ existing.disposers = []
40
+ existing.acceptors = []
41
+ return existing.api
42
+ }
43
+
44
+ const context = { acceptors: [], disposers: [], data: {} }
45
+
46
+ context.api = {
47
+ get data() {
48
+ return context.data
49
+ },
50
+ accept(callback) {
51
+ context.acceptors.push(typeof callback === 'function' ? callback : () => {})
52
+ },
53
+ dispose(callback) {
54
+ context.disposers.push(callback)
55
+ },
56
+ invalidate() {
57
+ location.reload()
58
+ },
59
+ }
60
+
61
+ contexts.set(url, context)
62
+ return context.api
63
+ }
64
+
65
+ /**
66
+ * Recharge un module et notifie ceux qui l'acceptent.
67
+ * @param {string} url
68
+ * @param {number} timestamp
69
+ */
70
+ async function applyUpdate(url, timestamp) {
71
+ const context = contexts.get(url)
72
+ if (!context || context.acceptors.length === 0) {
73
+ location.reload()
74
+ return
75
+ }
76
+
77
+ const acceptors = [...context.acceptors]
78
+ try {
79
+ const module = await import(url + (url.includes('?') ? '&' : '?') + 't=' + timestamp)
80
+ for (const accept of acceptors) accept(module)
81
+ console.log('[odoro] mis a jour', url)
82
+ } catch (cause) {
83
+ console.error('[odoro] echec de la mise a jour de', url, cause)
84
+ location.reload()
85
+ }
86
+ }
87
+
88
+ /** Retire la surcouche d'erreur si elle est affichee. */
89
+ function clearOverlay() {
90
+ document.getElementById(OVERLAY_ID)?.remove()
91
+ }
92
+
93
+ /**
94
+ * Affiche une erreur de compilation par-dessus la page.
95
+ * @param {string} message
96
+ * @param {string | undefined} file
97
+ */
98
+ function showOverlay(message, file) {
99
+ clearOverlay()
100
+ const overlay = document.createElement('div')
101
+ overlay.id = OVERLAY_ID
102
+ overlay.setAttribute('role', 'alert')
103
+ overlay.style.cssText = [
104
+ 'position:fixed',
105
+ 'inset:0',
106
+ 'z-index:2147483647',
107
+ 'padding:2rem',
108
+ 'overflow:auto',
109
+ 'background:rgba(10,10,16,0.94)',
110
+ 'color:#ffd7d7',
111
+ 'font:14px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace',
112
+ 'white-space:pre-wrap',
113
+ ].join(';')
114
+
115
+ const title = document.createElement('div')
116
+ title.textContent = file ? 'Erreur de compilation — ' + file : 'Erreur de compilation'
117
+ title.style.cssText = 'font-weight:700;margin-bottom:1rem;color:#ff9d9d'
118
+
119
+ const body = document.createElement('div')
120
+ body.textContent = message
121
+
122
+ const hint = document.createElement('div')
123
+ hint.textContent = 'Corrigez le fichier : cette surcouche disparaitra d elle-meme.'
124
+ hint.style.cssText = 'margin-top:1.5rem;opacity:0.6'
125
+
126
+ overlay.append(title, body, hint)
127
+ document.body.appendChild(overlay)
128
+ }
129
+
130
+ const source = new EventSource('__HMR_STREAM_PATH__')
131
+
132
+ source.addEventListener('message', (event) => {
133
+ const payload = JSON.parse(event.data)
134
+
135
+ switch (payload.type) {
136
+ case 'connected':
137
+ console.log('[odoro] rechargement a chaud connecte')
138
+ break
139
+ case 'update':
140
+ clearOverlay()
141
+ for (const update of payload.updates) {
142
+ void applyUpdate(update.url, update.timestamp)
143
+ }
144
+ break
145
+ case 'full-reload':
146
+ location.reload()
147
+ break
148
+ case 'error':
149
+ showOverlay(payload.message, payload.file)
150
+ break
151
+ default:
152
+ break
153
+ }
154
+ })
155
+
156
+ source.addEventListener('error', () => {
157
+ // EventSource se reconnecte seul ; on ne signale que la perte prolongee.
158
+ if (source.readyState === EventSource.CLOSED) {
159
+ console.warn('[odoro] connexion de rechargement perdue')
160
+ }
161
+ })
162
+ `.replace("__HMR_STREAM_PATH__", HMR_STREAM_PATH);
163
+ function hotPreamble(url) {
164
+ return `import { createHotContext as __odoroHot } from ${JSON.stringify(
165
+ HMR_CLIENT_PATH
166
+ )}
167
+ import.meta.hot = __odoroHot(${JSON.stringify(url)})
168
+ `;
169
+ }
170
+ var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
171
+ var RESERVED = /* @__PURE__ */ new Set([
172
+ "break",
173
+ "case",
174
+ "catch",
175
+ "class",
176
+ "const",
177
+ "continue",
178
+ "debugger",
179
+ "default",
180
+ "delete",
181
+ "do",
182
+ "else",
183
+ "enum",
184
+ "export",
185
+ "extends",
186
+ "false",
187
+ "finally",
188
+ "for",
189
+ "function",
190
+ "if",
191
+ "import",
192
+ "in",
193
+ "instanceof",
194
+ "new",
195
+ "null",
196
+ "return",
197
+ "super",
198
+ "switch",
199
+ "this",
200
+ "throw",
201
+ "true",
202
+ "try",
203
+ "typeof",
204
+ "var",
205
+ "void",
206
+ "while",
207
+ "with",
208
+ "yield",
209
+ "let",
210
+ "static",
211
+ "await"
212
+ ]);
213
+ function isCommonJsFile(file) {
214
+ if (file.endsWith(".mjs")) return false;
215
+ if (file.endsWith(".cjs")) return true;
216
+ let directory = dirname(file);
217
+ for (let depth = 0; depth < 20; depth += 1) {
218
+ const manifest = join(directory, "package.json");
219
+ if (existsSync(manifest)) {
220
+ try {
221
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
222
+ return parsed.type !== "module";
223
+ } catch {
224
+ return true;
225
+ }
226
+ }
227
+ const parent = dirname(directory);
228
+ if (parent === directory) break;
229
+ directory = parent;
230
+ }
231
+ return true;
232
+ }
233
+ function inspectDependency(specifier, root) {
234
+ const plain = { specifier, needsInterop: false, namedExports: [] };
235
+ const require2 = createRequire(join(root, "index.js"));
236
+ let resolved;
237
+ try {
238
+ resolved = require2.resolve(specifier);
239
+ } catch {
240
+ return plain;
241
+ }
242
+ if (!isCommonJsFile(resolved)) return plain;
243
+ let loaded;
244
+ try {
245
+ loaded = require2(specifier);
246
+ } catch {
247
+ return plain;
248
+ }
249
+ if (typeof loaded !== "object" && typeof loaded !== "function") return plain;
250
+ if (loaded === null) return plain;
251
+ const namedExports = Object.keys(loaded).filter(
252
+ (key) => key !== "default" && IDENTIFIER.test(key) && !RESERVED.has(key)
253
+ );
254
+ return { specifier, needsInterop: true, namedExports };
255
+ }
256
+ function renderInteropProxy(info2) {
257
+ const lines = [
258
+ `import cjs from ${JSON.stringify(info2.specifier)}`,
259
+ "export default cjs"
260
+ ];
261
+ if (info2.namedExports.length > 0) {
262
+ lines.push(`export const { ${info2.namedExports.join(", ")} } = cjs`);
263
+ }
264
+ return `${lines.join("\n")}
265
+ `;
266
+ }
267
+ var DEPS_PREFIX = "/@deps/";
268
+ var INTERNAL_PREFIX = "/@odoro/";
269
+ var STYLE_EXTENSIONS = [".css"];
270
+ var ASSET_EXTENSIONS = [
271
+ ".svg",
272
+ ".png",
273
+ ".jpg",
274
+ ".jpeg",
275
+ ".gif",
276
+ ".webp",
277
+ ".avif",
278
+ ".ico",
279
+ ".woff",
280
+ ".woff2",
281
+ ".mp4",
282
+ ".webm"
283
+ ];
284
+ function depFileName(specifier) {
285
+ return `${specifier.replace(/^@/, "").split("/").join("_")}.js`;
286
+ }
287
+ function isBareSpecifier(specifier) {
288
+ return !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("\\") && !/^[a-zA-Z]:[\\/]/.test(specifier) && !specifier.startsWith("data:") && !specifier.startsWith("http:") && !specifier.startsWith("https:");
289
+ }
290
+ function hasExtension(path, extensions) {
291
+ const clean = path.split("?")[0] ?? path;
292
+ return extensions.some((extension) => clean.toLowerCase().endsWith(extension));
293
+ }
294
+ function fileToUrl(file, root) {
295
+ const relativePath = relative(root, file).split("\\").join("/");
296
+ if (!relativePath.startsWith("..")) return `/${relativePath}`;
297
+ return `/@fs/${file.split("\\").join("/").replace(/^\//, "")}`;
298
+ }
299
+ function urlToFile(url, root) {
300
+ const path = (url.split("?")[0] ?? url).split("#")[0] ?? url;
301
+ if (path.startsWith("/@fs/")) {
302
+ const absolute = path.slice("/@fs/".length);
303
+ return /^[a-zA-Z]:/.test(absolute) ? absolute : `/${absolute}`;
304
+ }
305
+ return resolve(root, `.${path}`);
306
+ }
307
+ function applyAlias(specifier, config) {
308
+ for (const [prefix, target] of Object.entries(config.alias)) {
309
+ if (specifier === prefix || specifier.startsWith(`${prefix}/`)) {
310
+ return resolve(
311
+ config.root,
312
+ target,
313
+ specifier.slice(prefix.length).replace(/^\//, "")
314
+ );
315
+ }
316
+ }
317
+ return specifier;
318
+ }
319
+ function externalizeImports(config, dependencies) {
320
+ return {
321
+ name: "odoro-externalize",
322
+ setup(builder) {
323
+ builder.onResolve({ filter: /.*/ }, async (args) => {
324
+ if (args.kind === "entry-point") return null;
325
+ if (args.pluginData?.resolving === true) {
326
+ return null;
327
+ }
328
+ if (args.path.startsWith(INTERNAL_PREFIX) || args.path.startsWith(DEPS_PREFIX)) {
329
+ return { path: args.path, external: true };
330
+ }
331
+ const aliased = applyAlias(args.path, config);
332
+ const isFileLike = hasExtension(aliased, STYLE_EXTENSIONS) || hasExtension(aliased, ASSET_EXTENSIONS);
333
+ if (isBareSpecifier(aliased) && !isFileLike) {
334
+ return { path: `${DEPS_PREFIX}${depFileName(aliased)}`, external: true };
335
+ }
336
+ const resolved = await builder.resolve(aliased, {
337
+ kind: "import-statement",
338
+ resolveDir: args.resolveDir,
339
+ importer: args.importer,
340
+ pluginData: { resolving: true }
341
+ });
342
+ if (resolved.errors.length > 0) {
343
+ return { path: args.path, external: true };
344
+ }
345
+ dependencies.add(resolved.path);
346
+ const url = fileToUrl(resolved.path, config.root);
347
+ return {
348
+ path: hasExtension(url, ASSET_EXTENSIONS) ? `${url}?import` : url,
349
+ external: true
350
+ };
351
+ });
352
+ }
353
+ };
354
+ }
355
+ async function transformModule(file, config, env) {
356
+ const dependencies = /* @__PURE__ */ new Set();
357
+ const result = await build({
358
+ entryPoints: [file],
359
+ bundle: true,
360
+ write: false,
361
+ format: "esm",
362
+ platform: "browser",
363
+ target: "es2022",
364
+ sourcemap: "inline",
365
+ jsx: "automatic",
366
+ jsxDev: true,
367
+ logLevel: "silent",
368
+ absWorkingDir: config.root,
369
+ define: {
370
+ "import.meta.env": JSON.stringify(env),
371
+ "process.env.NODE_ENV": JSON.stringify("development"),
372
+ ...config.define
373
+ },
374
+ plugins: [externalizeImports(config, dependencies)]
375
+ });
376
+ const code = result.outputFiles[0]?.text;
377
+ if (code === void 0) {
378
+ throw new Error(`[odoro] La compilation de "${file}" n'a produit aucun code.`);
379
+ }
380
+ return { code, dependencies: [...dependencies] };
381
+ }
382
+ function wrapStyle(url, css) {
383
+ return `const id = ${JSON.stringify(`odoro-style:${url}`)}
384
+ const css = ${JSON.stringify(css)}
385
+
386
+ let element = document.querySelector(\`style[data-odoro-id="\${id}"]\`)
387
+ if (element === null) {
388
+ element = document.createElement('style')
389
+ element.setAttribute('data-odoro-id', id)
390
+ document.head.appendChild(element)
391
+ }
392
+ element.textContent = css
393
+
394
+ import.meta.hot?.accept()
395
+ import.meta.hot?.dispose(() => {
396
+ // La feuille suivante recreera l'element : le retirer evite d'empiler les
397
+ // regles mortes a chaque rechargement.
398
+ element?.remove()
399
+ })
400
+ `;
401
+ }
402
+ function wrapAsset(url) {
403
+ return `export default ${JSON.stringify(url)}
404
+ `;
405
+ }
406
+
407
+ // src/dev/deps.ts
408
+ var MANIFEST = "manifest.json";
409
+ function collectBareImports(config, found) {
410
+ return {
411
+ name: "odoro-scan-deps",
412
+ setup(builder) {
413
+ builder.onResolve({ filter: /.*/ }, (args) => {
414
+ if (args.kind === "entry-point") return null;
415
+ const aliased = applyAlias(args.path, config);
416
+ if (hasExtension(aliased, STYLE_EXTENSIONS) || hasExtension(aliased, ASSET_EXTENSIONS)) {
417
+ return { path: aliased, external: true };
418
+ }
419
+ if (!isBareSpecifier(aliased)) return null;
420
+ found.add(aliased);
421
+ return { path: aliased, external: true };
422
+ });
423
+ }
424
+ };
425
+ }
426
+ async function scanDependencies(config, entries) {
427
+ const found = /* @__PURE__ */ new Set();
428
+ const existing = entries.filter((entry) => existsSync(entry));
429
+ if (existing.length === 0) return [];
430
+ await build({
431
+ entryPoints: [...existing],
432
+ bundle: true,
433
+ write: false,
434
+ format: "esm",
435
+ platform: "browser",
436
+ logLevel: "silent",
437
+ absWorkingDir: config.root,
438
+ jsx: "automatic",
439
+ // Le serveur compile en JSX de developpement : sans ce reglage, le
440
+ // parcours chercherait `react/jsx-runtime` la ou le navigateur demandera
441
+ // `react/jsx-dev-runtime`, et la dependance manquerait a l'appel.
442
+ jsxDev: true,
443
+ plugins: [collectBareImports(config, found)]
444
+ });
445
+ return [...found].sort();
446
+ }
447
+ async function optimizeDeps(config, specifiers, force = false) {
448
+ const directory = join(config.root, "node_modules", ".odoro", "deps");
449
+ const sorted = [...specifiers].sort();
450
+ const hash = createHash("sha256").update(JSON.stringify(sorted)).update(await lockfileFingerprint(config.root)).update(entriesFingerprint(config.root, sorted)).update(engineVersion()).digest("hex").slice(0, 16);
451
+ const manifestPath = join(directory, MANIFEST);
452
+ if (!force && existsSync(manifestPath)) {
453
+ const previous = JSON.parse(await readFile(manifestPath, "utf8"));
454
+ if (previous.hash === hash) {
455
+ return { directory, specifiers: previous.specifiers, rebuilt: false };
456
+ }
457
+ }
458
+ await rm(directory, { recursive: true, force: true });
459
+ await mkdir(directory, { recursive: true });
460
+ if (sorted.length > 0) {
461
+ const proxies = join(directory, "proxies");
462
+ await mkdir(proxies, { recursive: true });
463
+ const entryPoints = [];
464
+ for (const specifier of sorted) {
465
+ const out = depFileName(specifier).replace(/\.js$/, "");
466
+ const info2 = inspectDependency(specifier, config.root);
467
+ if (!info2.needsInterop) {
468
+ entryPoints.push({ in: specifier, out });
469
+ continue;
470
+ }
471
+ const proxy = join(proxies, `${out}.js`);
472
+ await writeFile(proxy, renderInteropProxy(info2), "utf8");
473
+ entryPoints.push({ in: proxy, out });
474
+ }
475
+ await build({
476
+ // Les noms de sortie sont imposes : un specificateur a sous-chemin
477
+ // produirait sinon une arborescence, et deux paquets differents
478
+ // pourraient se disputer le meme nom de fichier.
479
+ entryPoints,
480
+ bundle: true,
481
+ format: "esm",
482
+ platform: "browser",
483
+ target: "es2022",
484
+ splitting: true,
485
+ outdir: directory,
486
+ absWorkingDir: config.root,
487
+ logLevel: "silent",
488
+ define: { "process.env.NODE_ENV": JSON.stringify("development") },
489
+ loader: { ".woff": "file", ".woff2": "file", ".svg": "dataurl" }
490
+ });
491
+ await rm(proxies, { recursive: true, force: true });
492
+ }
493
+ const manifest = { hash, specifiers: sorted };
494
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
495
+ return { directory, specifiers: sorted, rebuilt: true };
496
+ }
497
+ var cachedVersion;
498
+ function engineVersion() {
499
+ if (cachedVersion !== void 0) return cachedVersion;
500
+ let directory = dirname(fileURLToPath(import.meta.url));
501
+ for (let depth = 0; depth < 6; depth += 1) {
502
+ const manifest = join(directory, "package.json");
503
+ if (existsSync(manifest)) {
504
+ try {
505
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
506
+ if (parsed.name === "odoro") {
507
+ cachedVersion = parsed.version ?? "inconnue";
508
+ return cachedVersion;
509
+ }
510
+ } catch {
511
+ break;
512
+ }
513
+ }
514
+ const parent = dirname(directory);
515
+ if (parent === directory) break;
516
+ directory = parent;
517
+ }
518
+ cachedVersion = "inconnue";
519
+ return cachedVersion;
520
+ }
521
+ function entriesFingerprint(root, specifiers) {
522
+ const resolver = createRequire(pathToFileURL(join(root, "package.json")));
523
+ const parts = [];
524
+ for (const name of packageNames(specifiers)) {
525
+ parts.push(`${name}:${packageFingerprint(name, resolver)}`);
526
+ }
527
+ return parts.join("|");
528
+ }
529
+ function packageNames(specifiers) {
530
+ const names = /* @__PURE__ */ new Set();
531
+ for (const specifier of specifiers) {
532
+ const segments = specifier.split("/");
533
+ names.add(
534
+ specifier.startsWith("@") && segments.length > 1 ? `${segments[0] ?? ""}/${segments[1] ?? ""}` : segments[0] ?? specifier
535
+ );
536
+ }
537
+ return [...names].sort();
538
+ }
539
+ function packageFingerprint(name, resolver) {
540
+ let manifestPath;
541
+ try {
542
+ manifestPath = resolver.resolve(`${name}/package.json`);
543
+ } catch {
544
+ return "introuvable";
545
+ }
546
+ let manifest;
547
+ try {
548
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
549
+ } catch {
550
+ return "illisible";
551
+ }
552
+ const directory = dirname(manifestPath);
553
+ const targets = /* @__PURE__ */ new Set();
554
+ const collect = (node) => {
555
+ if (typeof node === "string") {
556
+ if (node.startsWith("./")) targets.add(node);
557
+ return;
558
+ }
559
+ if (typeof node === "object" && node !== null) {
560
+ for (const value of Object.values(node)) collect(value);
561
+ }
562
+ };
563
+ collect(manifest.exports);
564
+ collect(manifest.main);
565
+ collect(manifest.module);
566
+ const parts = [String(statSafe(manifestPath))];
567
+ for (const target of [...targets].sort()) {
568
+ parts.push(`${target}:${String(statSafe(join(directory, target)))}`);
569
+ }
570
+ return parts.join(",");
571
+ }
572
+ function statSafe(path) {
573
+ try {
574
+ const stats = statSync(path);
575
+ return `${String(stats.mtimeMs)}-${String(stats.size)}`;
576
+ } catch {
577
+ return "0";
578
+ }
579
+ }
580
+ async function lockfileFingerprint(root) {
581
+ for (const name of ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock"]) {
582
+ const file = join(root, name);
583
+ if (!existsSync(file)) continue;
584
+ return createHash("sha256").update(await readFile(file)).digest("hex");
585
+ }
586
+ return "sans-verrou";
587
+ }
588
+
589
+ // src/dev/graph.ts
590
+ function detectSelfAccepting(source) {
591
+ return /import\s*\.\s*meta\s*\.\s*hot\s*\??\s*\.\s*accept\s*\(/.test(source);
592
+ }
593
+ var ModuleGraph = class {
594
+ nodes = /* @__PURE__ */ new Map();
595
+ /** Recupere un module, ou le cree s'il est inconnu. */
596
+ ensure(file, url) {
597
+ const existing = this.nodes.get(file);
598
+ if (existing !== void 0) return existing;
599
+ const node = {
600
+ file,
601
+ url,
602
+ importers: /* @__PURE__ */ new Set(),
603
+ imported: /* @__PURE__ */ new Set(),
604
+ selfAccepting: false,
605
+ code: void 0,
606
+ timestamp: Date.now()
607
+ };
608
+ this.nodes.set(file, node);
609
+ return node;
610
+ }
611
+ /** Recupere un module deja connu. */
612
+ get(file) {
613
+ return this.nodes.get(file);
614
+ }
615
+ /** Nombre de modules connus. */
616
+ get size() {
617
+ return this.nodes.size;
618
+ }
619
+ /**
620
+ * Remplace la liste des dependances d'un module, en tenant a jour les
621
+ * relations inverses.
622
+ */
623
+ setDependencies(file, dependencies) {
624
+ const node = this.nodes.get(file);
625
+ if (node === void 0) return;
626
+ for (const previous of node.imported) {
627
+ if (!dependencies.includes(previous)) {
628
+ this.nodes.get(previous)?.importers.delete(file);
629
+ }
630
+ }
631
+ node.imported.clear();
632
+ for (const dependency of dependencies) {
633
+ node.imported.add(dependency);
634
+ const target = this.nodes.get(dependency);
635
+ if (target !== void 0) target.importers.add(file);
636
+ }
637
+ }
638
+ /**
639
+ * Invalide un module et remonte la chaine de ses importateurs jusqu'a
640
+ * trouver, sur chaque branche, un module qui accepte les mises a jour.
641
+ *
642
+ * @returns Les modules a recharger cote client. Un tableau vide signifie
643
+ * qu'aucune frontiere n'accepte la mise a jour : il faut recharger la page.
644
+ *
645
+ * @example
646
+ * const boundaries = graph.invalidate('/projet/src/App.css')
647
+ */
648
+ invalidate(file) {
649
+ const boundaries = [];
650
+ const seen = /* @__PURE__ */ new Set();
651
+ const timestamp = Date.now();
652
+ const walk = (current) => {
653
+ if (seen.has(current)) return true;
654
+ seen.add(current);
655
+ const node = this.nodes.get(current);
656
+ if (node === void 0) return false;
657
+ node.code = void 0;
658
+ node.timestamp = timestamp;
659
+ if (node.selfAccepting) {
660
+ boundaries.push(node);
661
+ return true;
662
+ }
663
+ if (node.importers.size === 0) return false;
664
+ let handled = true;
665
+ for (const importer of node.importers) {
666
+ if (!walk(importer)) handled = false;
667
+ }
668
+ return handled;
669
+ };
670
+ return walk(file) ? boundaries : [];
671
+ }
672
+ /** Oublie tous les modules. */
673
+ clear() {
674
+ this.nodes.clear();
675
+ }
676
+ };
677
+ var REFRESH_RUNTIME_PATH = "/@odoro/react-refresh";
678
+ var CANDIDATE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js", ".mjs"];
679
+ function isRefreshCandidate(file) {
680
+ const normalized = file.split("\\").join("/");
681
+ if (normalized.includes("/node_modules/")) return false;
682
+ return CANDIDATE_EXTENSIONS.some((extension) => normalized.endsWith(extension));
683
+ }
684
+ async function applyReactRefresh(code, file) {
685
+ const result = await transformAsync(code, {
686
+ filename: file,
687
+ babelrc: false,
688
+ configFile: false,
689
+ // La carte de source produite par la compilation precedente est reprise et
690
+ // fusionnee : sans cela, les numeros de ligne du debogueur designeraient le
691
+ // code instrumente plutot que la source.
692
+ inputSourceMap: true,
693
+ sourceMaps: "inline",
694
+ // `skipEnvCheck` leve un garde-fou destine aux configurations globales,
695
+ // qui refuse la transformation hors de NODE_ENV=development. Ici c'est le
696
+ // point d'application qui garantit la regle : cette fonction n'est appelee
697
+ // que par le serveur de developpement, jamais par la compilation de
698
+ // production. Sans cela, un `NODE_ENV=production odoro dev` — ou une suite
699
+ // de tests — echouerait au lieu de simplement instrumenter.
700
+ plugins: [[reactRefreshPlugin, { skipEnvCheck: true }]],
701
+ parserOpts: { sourceType: "module" }
702
+ });
703
+ return result?.code ?? code;
704
+ }
705
+ function hasRegisteredComponent(code) {
706
+ return code.includes("$RefreshReg$(");
707
+ }
708
+ function refreshPreamble(id) {
709
+ return `import * as __odoroRefresh from ${JSON.stringify(REFRESH_RUNTIME_PATH)}
710
+ const __odoroPrevReg = window.$RefreshReg$
711
+ const __odoroPrevSig = window.$RefreshSig$
712
+ window.$RefreshReg$ = (type, name) => __odoroRefresh.register(type, ${JSON.stringify(id)} + ' ' + name)
713
+ window.$RefreshSig$ = __odoroRefresh.createSignature
714
+ `;
715
+ }
716
+ function refreshEpilogue(id) {
717
+ return `
718
+ window.$RefreshReg$ = __odoroPrevReg
719
+ window.$RefreshSig$ = __odoroPrevSig
720
+
721
+ void import(import.meta.url).then((__odoroCurrent) => {
722
+ __odoroRefresh.registerExports(${JSON.stringify(id)}, __odoroCurrent)
723
+ import.meta.hot?.accept((__odoroNext) => {
724
+ if (!__odoroNext) return
725
+ const refus = __odoroRefresh.checkBoundary(__odoroCurrent, __odoroNext)
726
+ if (refus !== null) import.meta.hot.invalidate(refus)
727
+ else {
728
+ __odoroRefresh.registerExports(${JSON.stringify(id)}, __odoroNext)
729
+ __odoroRefresh.enqueueUpdate()
730
+ }
731
+ })
732
+ })
733
+ `;
734
+ }
735
+ var REFRESH_RUNTIME_SOURCE = `import runtime from 'react-refresh/runtime'
736
+
737
+ // Le crochet doit etre installe **avant** que React ne soit charge : c'est par
738
+ // lui que React signale les composants qu'il rend.
739
+ runtime.injectIntoGlobalHook(window)
740
+
741
+ // Valeurs neutres : un module non instrumente doit pouvoir s'evaluer sans que
742
+ // ces fonctions existent vraiment.
743
+ window.$RefreshReg$ = () => {}
744
+ window.$RefreshSig$ = () => (type) => type
745
+
746
+ export const register = runtime.register
747
+ export const createSignature = runtime.createSignatureFunctionForTransform
748
+
749
+ /**
750
+ * Enregistre les exports d'un module qui ressemblent a des composants.
751
+ *
752
+ * L'enregistrement par nom d'export complete celui pose dans le corps du
753
+ * module : un composant re-exporte depuis un autre fichier n'y apparaitrait
754
+ * pas.
755
+ */
756
+ export function registerExports(id, exports) {
757
+ for (const key of Object.keys(exports)) {
758
+ const value = exports[key]
759
+ if (runtime.isLikelyComponentType(value)) {
760
+ runtime.register(value, id + ' export ' + key)
761
+ }
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Verifie qu'un module peut etre remplace a chaud.
767
+ *
768
+ * @returns null si le remplacement est sur, sinon la raison du refus.
769
+ */
770
+ export function checkBoundary(previous, next) {
771
+ const before = Object.keys(previous)
772
+ const after = Object.keys(next)
773
+
774
+ if (after.length === 0) return 'le module n exporte plus rien'
775
+
776
+ for (const key of after) {
777
+ if (!before.includes(key)) return 'nouvel export : ' + key
778
+ }
779
+ for (const key of before) {
780
+ if (!after.includes(key)) return 'export retire : ' + key
781
+ }
782
+
783
+ for (const key of after) {
784
+ const value = next[key]
785
+ if (runtime.isLikelyComponentType(value)) continue
786
+ // Un export qui n'est pas un composant ne peut etre conserve que s'il n'a
787
+ // pas change : sinon ses consommateurs garderaient l'ancienne valeur.
788
+ if (previous[key] !== value) return 'export non-composant modifie : ' + key
789
+ }
790
+
791
+ return null
792
+ }
793
+
794
+ let planned
795
+ const DEBOUNCE = 16
796
+
797
+ /** Regroupe les mises a jour d'une meme salve en un seul rafraichissement. */
798
+ export function enqueueUpdate() {
799
+ clearTimeout(planned)
800
+ planned = setTimeout(() => {
801
+ planned = undefined
802
+ runtime.performReactRefresh()
803
+ }, DEBOUNCE)
804
+ }
805
+ `;
806
+ var REFRESH_HTML_TAG = `<script type="module" src="${REFRESH_RUNTIME_PATH}"></script>`;
807
+ var cachedRuntime;
808
+ async function bundleRefreshRuntime() {
809
+ if (cachedRuntime !== void 0) return cachedRuntime;
810
+ const result = await build({
811
+ stdin: {
812
+ contents: REFRESH_RUNTIME_SOURCE,
813
+ resolveDir: dirname(fileURLToPath(import.meta.url)),
814
+ loader: "js",
815
+ sourcefile: "odoro-react-refresh.js"
816
+ },
817
+ bundle: true,
818
+ write: false,
819
+ format: "esm",
820
+ platform: "browser",
821
+ target: "es2022",
822
+ logLevel: "silent",
823
+ define: { "process.env.NODE_ENV": JSON.stringify("development") }
824
+ });
825
+ cachedRuntime = result.outputFiles[0]?.text ?? "";
826
+ return cachedRuntime;
827
+ }
828
+
829
+ // src/dev/server.ts
830
+ var MIME = {
831
+ ".html": "text/html; charset=utf-8",
832
+ ".js": "text/javascript; charset=utf-8",
833
+ ".mjs": "text/javascript; charset=utf-8",
834
+ ".json": "application/json; charset=utf-8",
835
+ ".css": "text/css; charset=utf-8",
836
+ ".svg": "image/svg+xml",
837
+ ".png": "image/png",
838
+ ".jpg": "image/jpeg",
839
+ ".jpeg": "image/jpeg",
840
+ ".gif": "image/gif",
841
+ ".webp": "image/webp",
842
+ ".avif": "image/avif",
843
+ ".ico": "image/x-icon",
844
+ ".woff": "font/woff",
845
+ ".woff2": "font/woff2",
846
+ ".mp4": "video/mp4",
847
+ ".webm": "video/webm",
848
+ ".txt": "text/plain; charset=utf-8"
849
+ };
850
+ var SCRIPT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
851
+ function cleanUrl(url) {
852
+ return (url.split("?")[0] ?? url).split("#")[0] ?? url;
853
+ }
854
+ function buildEnv(config) {
855
+ const env = {
856
+ MODE: "development",
857
+ DEV: true,
858
+ PROD: false,
859
+ BASE_URL: config.base
860
+ };
861
+ for (const [key, value] of Object.entries(process.env)) {
862
+ if (key.startsWith(config.envPrefix) && value !== void 0) env[key] = value;
863
+ }
864
+ return env;
865
+ }
866
+ function extractEntries(html, root) {
867
+ const entries = [];
868
+ for (const match of html.matchAll(
869
+ /<script[^>]*type=["']module["'][^>]*src=["']([^"']+)["']/gi
870
+ )) {
871
+ const source = match[1];
872
+ if (source === void 0 || /^https?:/.test(source)) continue;
873
+ entries.push(resolve(root, source.replace(/^\//, "")));
874
+ }
875
+ return entries;
876
+ }
877
+ function injectClient(html) {
878
+ const tags = [
879
+ REFRESH_HTML_TAG,
880
+ `<script type="module" src="${HMR_CLIENT_PATH}"></script>`
881
+ ].join("\n ");
882
+ if (html.includes("</head>")) return html.replace("</head>", ` ${tags}
883
+ </head>`);
884
+ return `${tags}
885
+ ${html}`;
886
+ }
887
+ async function startDevServer(config) {
888
+ const started = Date.now();
889
+ const graph = new ModuleGraph();
890
+ const env = buildEnv(config);
891
+ const clients = /* @__PURE__ */ new Set();
892
+ const indexFile = join(config.root, "index.html");
893
+ if (!existsSync(indexFile)) {
894
+ throw new Error(`[odoro] Aucun "index.html" a la racine du projet (${config.root}).`);
895
+ }
896
+ const refreshRuntime = await bundleRefreshRuntime();
897
+ const entries = extractEntries(await readFile(indexFile, "utf8"), config.root);
898
+ const specifiers = await scanDependencies(config, entries);
899
+ const deps = await optimizeDeps(config, specifiers);
900
+ if (deps.rebuilt && specifiers.length > 0) {
901
+ info(`${specifiers.length} dependances pre-compilees`);
902
+ }
903
+ const broadcast = (message) => {
904
+ const payload = `data: ${JSON.stringify(message)}
905
+
906
+ `;
907
+ for (const client of clients) client.write(payload);
908
+ };
909
+ const send = (response, body, type, status = 200) => {
910
+ response.writeHead(status, {
911
+ "Content-Type": type,
912
+ "Cache-Control": "no-cache"
913
+ });
914
+ response.end(body);
915
+ };
916
+ const serveScript = async (response, file) => {
917
+ const url2 = fileToUrl(file, config.root);
918
+ const node = graph.ensure(file, url2);
919
+ if (node.code === void 0) {
920
+ const source = await readFile(file, "utf8");
921
+ node.selfAccepting = detectSelfAccepting(source);
922
+ const { code, dependencies } = await transformModule(file, config, env);
923
+ graph.setDependencies(file, dependencies);
924
+ for (const dependency of dependencies) {
925
+ graph.ensure(dependency, fileToUrl(dependency, config.root)).importers.add(file);
926
+ }
927
+ let body = code;
928
+ if (isRefreshCandidate(file)) {
929
+ const instrumented = await applyReactRefresh(code, file);
930
+ if (hasRegisteredComponent(instrumented)) {
931
+ body = refreshPreamble(url2) + instrumented + refreshEpilogue(url2);
932
+ node.selfAccepting = true;
933
+ }
934
+ }
935
+ node.code = hotPreamble(url2) + body;
936
+ }
937
+ send(response, node.code, MIME[".js"]);
938
+ };
939
+ const serveStyle = async (response, file, direct) => {
940
+ const css = await readFile(file, "utf8");
941
+ if (direct) {
942
+ send(response, css, MIME[".css"]);
943
+ return;
944
+ }
945
+ const url2 = fileToUrl(file, config.root);
946
+ const node = graph.ensure(file, url2);
947
+ node.selfAccepting = true;
948
+ node.code = hotPreamble(url2) + wrapStyle(url2, css);
949
+ send(response, node.code, MIME[".js"]);
950
+ };
951
+ const serveFile = (response, file) => {
952
+ const type = MIME[extname(file).toLowerCase()] ?? "application/octet-stream";
953
+ response.writeHead(200, { "Content-Type": type, "Cache-Control": "no-cache" });
954
+ createReadStream(file).pipe(response);
955
+ };
956
+ const serveHtml = async (response) => {
957
+ const html = await readFile(indexFile, "utf8");
958
+ send(response, injectClient(html), MIME[".html"]);
959
+ };
960
+ const forward = (incoming, response, target) => {
961
+ const url2 = new URL(incoming.url ?? "/", target);
962
+ const proxied = request(
963
+ {
964
+ protocol: url2.protocol,
965
+ hostname: url2.hostname,
966
+ port: url2.port,
967
+ path: `${url2.pathname}${url2.search}`,
968
+ method: incoming.method,
969
+ headers: { ...incoming.headers, host: url2.host }
970
+ },
971
+ (upstream) => {
972
+ response.writeHead(upstream.statusCode ?? 502, upstream.headers);
973
+ upstream.pipe(response);
974
+ }
975
+ );
976
+ proxied.on("error", (cause) => {
977
+ warn(`proxy indisponible : ${target}`);
978
+ send(response, `Proxy indisponible : ${String(cause)}`, "text/plain", 502);
979
+ });
980
+ incoming.pipe(proxied);
981
+ };
982
+ const server = createServer((incoming, response) => {
983
+ void (async () => {
984
+ const url2 = incoming.url ?? "/";
985
+ const path = cleanUrl(url2);
986
+ try {
987
+ for (const [prefix, target] of Object.entries(config.server.proxy)) {
988
+ if (path.startsWith(prefix)) {
989
+ forward(incoming, response, target);
990
+ return;
991
+ }
992
+ }
993
+ if (path === REFRESH_RUNTIME_PATH) {
994
+ send(response, refreshRuntime, MIME[".js"] ?? "text/javascript");
995
+ return;
996
+ }
997
+ if (path === HMR_CLIENT_PATH) {
998
+ send(response, HMR_CLIENT_SOURCE, MIME[".js"] ?? "text/javascript");
999
+ return;
1000
+ }
1001
+ if (path === HMR_STREAM_PATH) {
1002
+ response.writeHead(200, {
1003
+ "Content-Type": "text/event-stream",
1004
+ "Cache-Control": "no-cache",
1005
+ Connection: "keep-alive"
1006
+ });
1007
+ response.write(`data: ${JSON.stringify({ type: "connected" })}
1008
+
1009
+ `);
1010
+ clients.add(response);
1011
+ incoming.on("close", () => clients.delete(response));
1012
+ return;
1013
+ }
1014
+ if (path.startsWith(DEPS_PREFIX)) {
1015
+ const specifier = path.slice(DEPS_PREFIX.length);
1016
+ const last = specifier.split("/").pop() ?? specifier;
1017
+ const name = last.endsWith(".js") ? last : depFileName(last);
1018
+ const file2 = join(deps.directory, name);
1019
+ if (existsSync(file2)) {
1020
+ serveFile(response, file2);
1021
+ return;
1022
+ }
1023
+ send(
1024
+ response,
1025
+ `throw new Error(${JSON.stringify(
1026
+ `[odoro] Dependance non pre-compilee : "${specifier}". Relancez le serveur.`
1027
+ )})`,
1028
+ MIME[".js"] ?? "text/javascript"
1029
+ );
1030
+ return;
1031
+ }
1032
+ if (path === "/" || path === "/index.html") {
1033
+ await serveHtml(response);
1034
+ return;
1035
+ }
1036
+ if (path.startsWith(INTERNAL_PREFIX)) {
1037
+ send(response, "Introuvable", "text/plain", 404);
1038
+ return;
1039
+ }
1040
+ const file = urlToFile(path, config.root);
1041
+ if (existsSync(file) && statSync(file).isFile()) {
1042
+ if (hasExtension(path, STYLE_EXTENSIONS)) {
1043
+ await serveStyle(response, file, url2.includes("?direct"));
1044
+ return;
1045
+ }
1046
+ if (hasExtension(path, ASSET_EXTENSIONS)) {
1047
+ if (url2.includes("?import")) {
1048
+ send(response, wrapAsset(path), MIME[".js"] ?? "text/javascript");
1049
+ } else {
1050
+ serveFile(response, file);
1051
+ }
1052
+ return;
1053
+ }
1054
+ if (hasExtension(path, SCRIPT_EXTENSIONS)) {
1055
+ await serveScript(response, file);
1056
+ return;
1057
+ }
1058
+ serveFile(response, file);
1059
+ return;
1060
+ }
1061
+ const publicFile = join(config.publicDir, path.replace(/^\//, ""));
1062
+ if (existsSync(publicFile) && statSync(publicFile).isFile()) {
1063
+ serveFile(response, publicFile);
1064
+ return;
1065
+ }
1066
+ if (!extname(path)) {
1067
+ await serveHtml(response);
1068
+ return;
1069
+ }
1070
+ send(response, "Introuvable", "text/plain", 404);
1071
+ } catch (cause) {
1072
+ const message = cause instanceof Error ? cause.message : String(cause);
1073
+ error(`echec du traitement de ${path}`, cause);
1074
+ broadcast({ type: "error", message, file: path });
1075
+ send(response, `Erreur : ${message}`, "text/plain", 500);
1076
+ }
1077
+ })();
1078
+ });
1079
+ let pending;
1080
+ const changed = /* @__PURE__ */ new Set();
1081
+ const watcher = watch(config.root, { recursive: true }, (_event, filename) => {
1082
+ if (filename === null) return;
1083
+ const normalized = filename.split("\\").join("/");
1084
+ if (normalized.includes("node_modules/") || normalized.startsWith(".git/") || normalized.startsWith("dist/")) {
1085
+ return;
1086
+ }
1087
+ changed.add(join(config.root, filename));
1088
+ clearTimeout(pending);
1089
+ pending = setTimeout(() => {
1090
+ const files = [...changed];
1091
+ changed.clear();
1092
+ const updates = [];
1093
+ let reload = false;
1094
+ for (const file of files) {
1095
+ if (file === indexFile) {
1096
+ reload = true;
1097
+ continue;
1098
+ }
1099
+ const boundaries = graph.invalidate(file);
1100
+ if (boundaries.length === 0) {
1101
+ if (graph.get(file) !== void 0) reload = true;
1102
+ continue;
1103
+ }
1104
+ for (const boundary of boundaries) {
1105
+ updates.push({ url: boundary.url, timestamp: boundary.timestamp });
1106
+ }
1107
+ }
1108
+ if (reload) {
1109
+ info("rechargement de la page");
1110
+ broadcast({ type: "full-reload" });
1111
+ } else if (updates.length > 0) {
1112
+ info(
1113
+ `mise a jour a chaud : ${updates.map((update) => update.url).join(", ")}`
1114
+ );
1115
+ broadcast({ type: "update", updates });
1116
+ }
1117
+ }, 40);
1118
+ });
1119
+ await new Promise((resolveListen, rejectListen) => {
1120
+ server.once("error", rejectListen);
1121
+ server.listen(config.server.port, config.server.host, resolveListen);
1122
+ });
1123
+ const url = `http://${config.server.host}:${config.server.port}${config.base}`;
1124
+ success(`pret en ${duration(Date.now() - started)}`);
1125
+ info(` ${colors.cyan(url)}`);
1126
+ return {
1127
+ url,
1128
+ async close() {
1129
+ watcher.close();
1130
+ clearTimeout(pending);
1131
+ for (const client of clients) client.end();
1132
+ clients.clear();
1133
+ graph.clear();
1134
+ await new Promise((done) => server.close(() => done()));
1135
+ }
1136
+ };
1137
+ }
1138
+
1139
+ export { ModuleGraph, applyAlias, depFileName, detectSelfAccepting, extractEntries, injectClient, isBareSpecifier, optimizeDeps, scanDependencies, startDevServer };