@websublime/vite-plugin-open-api-server 0.24.0-next.3 → 0.24.0-next.5

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/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { existsSync } from 'fs';
2
1
  import { createRequire } from 'module';
3
- import path2, { dirname, join } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { createOpenApiServer, executeSeeds, mountInternalApi, mountDevToolsRoutes } from '@websublime/vite-plugin-open-api-core';
2
+ import { mountInternalApi, executeSeeds, createOpenApiServer, mountDevToolsRoutes } from '@websublime/vite-plugin-open-api-core';
6
3
  export { defineHandlers, defineSeeds } from '@websublime/vite-plugin-open-api-core';
7
4
  import pc from 'picocolors';
5
+ import path2, { dirname, join } from 'path';
8
6
  import fg from 'fast-glob';
7
+ import { existsSync } from 'fs';
8
+ import { fileURLToPath } from 'url';
9
9
  import { Hono } from 'hono';
10
10
  import { cors } from 'hono/cors';
11
11
 
@@ -329,6 +329,241 @@ function debounce(fn, delay) {
329
329
  }, delay);
330
330
  };
331
331
  }
332
+
333
+ // src/multi-proxy.ts
334
+ var DEVTOOLS_PROXY_PATH = "/_devtools";
335
+ var API_PROXY_PATH = "/_api";
336
+ var WS_PROXY_PATH = "/_ws";
337
+ function getProxyConfig(vite) {
338
+ if (!vite.config.server) {
339
+ return null;
340
+ }
341
+ vite.config.server.proxy ??= {};
342
+ return vite.config.server.proxy;
343
+ }
344
+ function configureMultiProxy(vite, instances, port) {
345
+ const proxyConfig = getProxyConfig(vite);
346
+ if (!proxyConfig) {
347
+ return;
348
+ }
349
+ const httpTarget = `http://localhost:${port}`;
350
+ for (const instance of instances) {
351
+ const prefix = instance.config.proxyPath;
352
+ proxyConfig[prefix] = {
353
+ target: httpTarget,
354
+ changeOrigin: true,
355
+ rewrite: (path4) => {
356
+ if (path4 !== prefix && !path4.startsWith(`${prefix}/`) && !path4.startsWith(`${prefix}?`))
357
+ return path4;
358
+ const rest = path4.slice(prefix.length);
359
+ if (rest === "" || rest === "/") return "/";
360
+ if (rest.startsWith("?")) return `/${rest}`;
361
+ return rest;
362
+ },
363
+ headers: { "x-spec-id": instance.id }
364
+ };
365
+ }
366
+ proxyConfig[DEVTOOLS_PROXY_PATH] = {
367
+ target: httpTarget,
368
+ changeOrigin: true
369
+ };
370
+ proxyConfig[API_PROXY_PATH] = {
371
+ target: httpTarget,
372
+ changeOrigin: true
373
+ };
374
+ proxyConfig[WS_PROXY_PATH] = {
375
+ target: `ws://localhost:${port}`,
376
+ changeOrigin: true,
377
+ ws: true
378
+ };
379
+ }
380
+
381
+ // src/types.ts
382
+ var ValidationError = class extends Error {
383
+ code;
384
+ constructor(code, message) {
385
+ super(message);
386
+ this.name = "ValidationError";
387
+ this.code = code;
388
+ }
389
+ };
390
+ function validateSpecs(specs) {
391
+ if (!specs || !Array.isArray(specs) || specs.length === 0) {
392
+ throw new ValidationError(
393
+ "SPECS_EMPTY",
394
+ "specs is required and must be a non-empty array of SpecConfig"
395
+ );
396
+ }
397
+ for (let i = 0; i < specs.length; i++) {
398
+ const spec = specs[i];
399
+ if (!spec || typeof spec !== "object") {
400
+ throw new ValidationError(
401
+ "SPEC_NOT_FOUND",
402
+ `specs[${i}]: must be a SpecConfig object, got ${spec === null ? "null" : typeof spec}`
403
+ );
404
+ }
405
+ if (!spec.spec || typeof spec.spec !== "string" || spec.spec.trim() === "") {
406
+ const identifier = spec.id ? ` (id: "${spec.id}")` : "";
407
+ throw new ValidationError(
408
+ "SPEC_NOT_FOUND",
409
+ `specs[${i}]${identifier}: spec field is required and must be a non-empty string (path or URL to OpenAPI spec)`
410
+ );
411
+ }
412
+ }
413
+ }
414
+ function resolveOptions(options) {
415
+ validateSpecs(options.specs);
416
+ return {
417
+ specs: options.specs.map((s) => ({
418
+ spec: s.spec,
419
+ // Placeholder — populated by orchestrator after document processing
420
+ id: s.id ?? "",
421
+ // Placeholder — populated by orchestrator after document processing
422
+ proxyPath: s.proxyPath ?? "",
423
+ // Preliminary — overwritten by deriveProxyPath() during orchestration
424
+ proxyPathSource: s.proxyPath?.trim() ? "explicit" : "auto",
425
+ handlersDir: s.handlersDir ?? "",
426
+ seedsDir: s.seedsDir ?? "",
427
+ idFields: s.idFields ?? {}
428
+ })),
429
+ port: options.port ?? 4e3,
430
+ enabled: options.enabled ?? true,
431
+ timelineLimit: options.timelineLimit ?? 500,
432
+ devtools: options.devtools ?? true,
433
+ cors: options.cors ?? true,
434
+ corsOrigin: options.corsOrigin ?? "*",
435
+ silent: options.silent ?? false,
436
+ logger: options.logger
437
+ };
438
+ }
439
+
440
+ // src/proxy-path.ts
441
+ var RESERVED_PROXY_PATHS = [
442
+ DEVTOOLS_PROXY_PATH,
443
+ API_PROXY_PATH,
444
+ WS_PROXY_PATH
445
+ ];
446
+ function deriveProxyPath(explicitPath, document, specId) {
447
+ if (explicitPath.trim()) {
448
+ return {
449
+ proxyPath: normalizeProxyPath(explicitPath.trim(), specId),
450
+ proxyPathSource: "explicit"
451
+ };
452
+ }
453
+ const servers = document.servers;
454
+ const serverUrl = servers?.[0]?.url?.trim();
455
+ if (!serverUrl) {
456
+ throw new ValidationError(
457
+ "PROXY_PATH_MISSING",
458
+ `[${specId}] Cannot derive proxyPath: no servers defined in the OpenAPI document. Set an explicit proxyPath in the spec configuration.`
459
+ );
460
+ }
461
+ let path4;
462
+ let parsedUrl;
463
+ try {
464
+ parsedUrl = new URL(serverUrl);
465
+ } catch {
466
+ }
467
+ if (parsedUrl) {
468
+ try {
469
+ path4 = decodeURIComponent(parsedUrl.pathname);
470
+ } catch {
471
+ path4 = parsedUrl.pathname;
472
+ }
473
+ } else {
474
+ path4 = serverUrl;
475
+ }
476
+ return {
477
+ proxyPath: normalizeProxyPath(path4, specId),
478
+ proxyPathSource: "auto"
479
+ };
480
+ }
481
+ function normalizeProxyPath(path4, specId) {
482
+ path4 = path4.trim();
483
+ const queryIdx = path4.indexOf("?");
484
+ const hashIdx = path4.indexOf("#");
485
+ const cutIdx = Math.min(
486
+ queryIdx >= 0 ? queryIdx : path4.length,
487
+ hashIdx >= 0 ? hashIdx : path4.length
488
+ );
489
+ let normalized = path4.slice(0, cutIdx);
490
+ normalized = normalized.startsWith("/") ? normalized : `/${normalized}`;
491
+ normalized = normalized.replace(/\/{2,}/g, "/");
492
+ const segments = normalized.split("/");
493
+ const resolved = [];
494
+ for (const segment of segments) {
495
+ if (segment === ".") {
496
+ continue;
497
+ }
498
+ if (segment === "..") {
499
+ if (resolved.length > 1) {
500
+ resolved.pop();
501
+ }
502
+ continue;
503
+ }
504
+ resolved.push(segment);
505
+ }
506
+ normalized = resolved.join("/") || "/";
507
+ if (normalized.length > 1 && normalized.endsWith("/")) {
508
+ normalized = normalized.slice(0, -1);
509
+ }
510
+ if (normalized === "/") {
511
+ throw new ValidationError(
512
+ "PROXY_PATH_TOO_BROAD",
513
+ `[${specId}] proxyPath "/" is too broad \u2014 it would capture all requests. Set a more specific proxyPath (e.g., "/api/v1").`
514
+ );
515
+ }
516
+ return normalized;
517
+ }
518
+ function validateUniqueProxyPaths(specs) {
519
+ const paths = /* @__PURE__ */ new Map();
520
+ for (const spec of specs) {
521
+ const path4 = spec.proxyPath?.trim();
522
+ if (!path4) {
523
+ continue;
524
+ }
525
+ validateNotReservedPath(path4, spec.id);
526
+ if (paths.has(path4)) {
527
+ throw new ValidationError(
528
+ "PROXY_PATH_DUPLICATE",
529
+ `Duplicate proxyPath "${path4}" used by specs "${paths.get(path4)}" and "${spec.id}". Each spec must have a unique proxyPath.`
530
+ );
531
+ }
532
+ paths.set(path4, spec.id);
533
+ }
534
+ validateNoPrefixOverlaps(paths);
535
+ }
536
+ function validateNotReservedPath(path4, specId) {
537
+ for (const reserved of RESERVED_PROXY_PATHS) {
538
+ if (path4 === reserved || path4.startsWith(`${reserved}/`) || reserved.startsWith(path4)) {
539
+ throw new ValidationError(
540
+ "PROXY_PATH_OVERLAP",
541
+ `[${specId}] proxyPath "${path4}" collides with reserved path "${reserved}" used by the shared DevTools/API/WebSocket service.`
542
+ );
543
+ }
544
+ }
545
+ }
546
+ function validateNoPrefixOverlaps(paths) {
547
+ const sortedPaths = Array.from(paths.entries()).sort(([a], [b]) => a.length - b.length);
548
+ for (let i = 0; i < sortedPaths.length; i++) {
549
+ for (let j = i + 1; j < sortedPaths.length; j++) {
550
+ const [shorter, shorterId] = sortedPaths[i];
551
+ const [longer, longerId] = sortedPaths[j];
552
+ if (longer.startsWith(`${shorter}/`)) {
553
+ throw new ValidationError(
554
+ "PROXY_PATH_OVERLAP",
555
+ `Overlapping proxyPaths: "${shorter}" (${shorterId}) is a prefix of "${longer}" (${longerId}). This would cause routing ambiguity.`
556
+ );
557
+ }
558
+ if (longer.startsWith(shorter)) {
559
+ throw new ValidationError(
560
+ "PROXY_PATH_PREFIX_COLLISION",
561
+ `Proxy path prefix collision: "${shorter}" (${shorterId}) is a string prefix of "${longer}" (${longerId}). Vite's proxy uses startsWith matching, so "${shorter}" would incorrectly capture requests meant for "${longer}".`
562
+ );
563
+ }
564
+ }
565
+ }
566
+ }
332
567
  async function loadSeeds(seedsDir, viteServer, cwd = process.cwd(), logger = console) {
333
568
  const seeds = /* @__PURE__ */ new Map();
334
569
  const absoluteDir = path2.resolve(cwd, seedsDir);
@@ -410,480 +645,56 @@ async function getSeedFiles(seedsDir, cwd = process.cwd()) {
410
645
  return files;
411
646
  }
412
647
 
413
- // src/types.ts
414
- var ValidationError = class extends Error {
415
- code;
416
- constructor(code, message) {
417
- super(message);
418
- this.name = "ValidationError";
419
- this.code = code;
648
+ // src/spec-id.ts
649
+ function slugify(input) {
650
+ return input.normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
651
+ }
652
+ function deriveSpecId(explicitId, document) {
653
+ if (explicitId.trim()) {
654
+ const id2 = slugify(explicitId);
655
+ if (!id2) {
656
+ throw new ValidationError(
657
+ "SPEC_ID_MISSING",
658
+ `Cannot derive spec ID: explicit id "${explicitId}" produces an empty slug. Please provide an id containing ASCII letters or numbers.`
659
+ );
660
+ }
661
+ return id2;
420
662
  }
421
- };
422
- function validateSpecs(specs) {
423
- if (!specs || !Array.isArray(specs) || specs.length === 0) {
663
+ const title = document.info?.title;
664
+ if (!title || !title.trim()) {
424
665
  throw new ValidationError(
425
- "SPECS_EMPTY",
426
- "specs is required and must be a non-empty array of SpecConfig"
666
+ "SPEC_ID_MISSING",
667
+ "Cannot derive spec ID: info.title is missing from the OpenAPI document. Please set an explicit id in the spec configuration."
427
668
  );
428
669
  }
429
- for (let i = 0; i < specs.length; i++) {
430
- const spec = specs[i];
431
- if (!spec.spec || typeof spec.spec !== "string" || spec.spec.trim() === "") {
432
- const identifier = spec.id ? ` (id: "${spec.id}")` : "";
433
- throw new ValidationError(
434
- "SPEC_NOT_FOUND",
435
- `specs[${i}]${identifier}: spec field is required and must be a non-empty string (path or URL to OpenAPI spec)`
436
- );
437
- }
438
- }
439
- }
440
- function resolveOptions(options) {
441
- validateSpecs(options.specs);
442
- return {
443
- specs: options.specs.map((s) => ({
444
- spec: s.spec,
445
- // Placeholder — populated by orchestrator after document processing
446
- id: s.id ?? "",
447
- // Placeholder — populated by orchestrator after document processing
448
- proxyPath: s.proxyPath ?? "",
449
- // Preliminary — overwritten by deriveProxyPath() during orchestration
450
- proxyPathSource: s.proxyPath?.trim() ? "explicit" : "auto",
451
- handlersDir: s.handlersDir ?? "",
452
- seedsDir: s.seedsDir ?? "",
453
- idFields: s.idFields ?? {}
454
- })),
455
- port: options.port ?? 4e3,
456
- enabled: options.enabled ?? true,
457
- timelineLimit: options.timelineLimit ?? 500,
458
- devtools: options.devtools ?? true,
459
- cors: options.cors ?? true,
460
- corsOrigin: options.corsOrigin ?? "*",
461
- silent: options.silent ?? false,
462
- logger: options.logger
463
- };
464
- }
465
-
466
- // src/plugin.ts
467
- var VIRTUAL_DEVTOOLS_TAB_ID = "virtual:open-api-devtools-tab";
468
- var RESOLVED_VIRTUAL_DEVTOOLS_TAB_ID = `\0${VIRTUAL_DEVTOOLS_TAB_ID}`;
469
- function openApiServer(options) {
470
- const resolvedOptions = resolveOptions(options);
471
- let server = null;
472
- let vite = null;
473
- let fileWatcher = null;
474
- let cwd = process.cwd();
475
- return {
476
- name: "vite-plugin-open-api-server",
477
- // Only active in dev mode
478
- apply: "serve",
479
- /**
480
- * Ensure @vue/devtools-api is available for the DevTools tab module
481
- *
482
- * The virtual module imports from '@vue/devtools-api', which Vite needs
483
- * to pre-bundle so it can be resolved at runtime in the browser, even
484
- * though the host app may not have it as a direct dependency.
485
- */
486
- config() {
487
- if (!resolvedOptions.devtools || !resolvedOptions.enabled) {
488
- return;
489
- }
490
- const require2 = createRequire(import.meta.url);
491
- try {
492
- require2.resolve("@vue/devtools-api");
493
- } catch {
494
- return;
495
- }
496
- return {
497
- optimizeDeps: {
498
- include: ["@vue/devtools-api"]
499
- }
500
- };
501
- },
502
- /**
503
- * Resolve the virtual module for DevTools tab registration
504
- */
505
- resolveId(id) {
506
- if (id === VIRTUAL_DEVTOOLS_TAB_ID) {
507
- return RESOLVED_VIRTUAL_DEVTOOLS_TAB_ID;
508
- }
509
- },
510
- /**
511
- * Load the virtual module that registers the custom Vue DevTools tab
512
- *
513
- * This code is served as a proper Vite module, allowing bare import
514
- * specifiers to be resolved through Vite's dependency pre-bundling.
515
- */
516
- load(id) {
517
- if (id === RESOLVED_VIRTUAL_DEVTOOLS_TAB_ID) {
518
- return `
519
- import { addCustomTab } from '@vue/devtools-api';
520
-
521
- try {
522
- // Route through Vite's proxy so it works in all environments
523
- const iframeSrc = window.location.origin + '/_devtools/';
524
-
525
- addCustomTab({
526
- name: 'vite-plugin-open-api-server',
527
- title: 'OpenAPI Server',
528
- icon: 'https://api.iconify.design/carbon:api-1.svg?width=24&height=24&color=%2394a3b8',
529
- view: {
530
- type: 'iframe',
531
- src: iframeSrc,
532
- },
533
- category: 'app',
534
- });
535
- } catch (e) {
536
- // @vue/devtools-api not available - expected when the package is not installed
537
- }
538
- `;
539
- }
540
- },
541
- /**
542
- * Configure the Vite dev server
543
- *
544
- * This hook is called when the dev server is created.
545
- * We use it to:
546
- * 1. Start the OpenAPI mock server
547
- * 2. Configure the Vite proxy to forward API requests
548
- * 3. Set up file watching for hot reload
549
- */
550
- async configureServer(viteServer) {
551
- vite = viteServer;
552
- cwd = viteServer.config.root;
553
- if (!resolvedOptions.enabled) {
554
- return;
555
- }
556
- try {
557
- const handlersResult = await loadHandlers(resolvedOptions.handlersDir, viteServer, cwd);
558
- const seedsResult = await loadSeeds(resolvedOptions.seedsDir, viteServer, cwd);
559
- let devtoolsSpaDir;
560
- if (resolvedOptions.devtools) {
561
- const pluginDir = dirname(fileURLToPath(import.meta.url));
562
- const spaDir = join(pluginDir, "devtools-spa");
563
- if (existsSync(spaDir)) {
564
- devtoolsSpaDir = spaDir;
565
- } else {
566
- resolvedOptions.logger?.warn?.(
567
- "[vite-plugin-open-api-server] DevTools SPA not found at",
568
- spaDir,
569
- '- serving placeholder. Run "pnpm build" to include the SPA.'
570
- );
571
- }
572
- }
573
- const opts = resolvedOptions;
574
- server = await createOpenApiServer({
575
- spec: opts.spec,
576
- port: opts.port,
577
- idFields: opts.idFields,
578
- handlers: handlersResult.handlers,
579
- // Seeds are populated via executeSeeds, not directly
580
- seeds: /* @__PURE__ */ new Map(),
581
- timelineLimit: opts.timelineLimit,
582
- devtools: opts.devtools,
583
- devtoolsSpaDir,
584
- cors: opts.cors,
585
- corsOrigin: opts.corsOrigin,
586
- logger: opts.logger
587
- });
588
- if (seedsResult.seeds.size > 0) {
589
- await executeSeeds(seedsResult.seeds, server.store, server.document);
590
- }
591
- await server.start();
592
- configureProxy(viteServer, resolvedOptions.proxyPath, resolvedOptions.port);
593
- const bannerInfo = extractBannerInfo(
594
- server.registry,
595
- {
596
- info: {
597
- title: server.document.info?.title ?? "OpenAPI Server",
598
- version: server.document.info?.version ?? "1.0.0"
599
- }
600
- },
601
- handlersResult.handlers.size,
602
- seedsResult.seeds.size,
603
- resolvedOptions
604
- );
605
- printBanner(bannerInfo, resolvedOptions);
606
- await setupFileWatching();
607
- } catch (error) {
608
- printError("Failed to start OpenAPI mock server", error, resolvedOptions);
609
- throw error;
610
- }
611
- },
612
- /**
613
- * Clean up when Vite server closes
614
- */
615
- async closeBundle() {
616
- await cleanup();
617
- },
618
- /**
619
- * Inject Vue DevTools custom tab registration script
620
- *
621
- * When devtools is enabled, this injects a script tag that loads the
622
- * virtual module for custom tab registration. Using a virtual module
623
- * (instead of an inline script) ensures that bare import specifiers
624
- * like `@vue/devtools-api` are resolved through Vite's module pipeline.
625
- */
626
- transformIndexHtml() {
627
- if (!resolvedOptions.devtools || !resolvedOptions.enabled) {
628
- return;
629
- }
630
- return [
631
- {
632
- tag: "script",
633
- attrs: { type: "module", src: `/@id/${VIRTUAL_DEVTOOLS_TAB_ID}` },
634
- injectTo: "head"
635
- }
636
- ];
637
- }
638
- };
639
- function configureProxy(vite2, proxyPath, port) {
640
- const serverConfig = vite2.config.server ?? {};
641
- const proxyConfig = serverConfig.proxy ?? {};
642
- const escapedPath = proxyPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
643
- const pathPrefixRegex = new RegExp(`^${escapedPath}`);
644
- proxyConfig[proxyPath] = {
645
- target: `http://localhost:${port}`,
646
- changeOrigin: true,
647
- // Remove the proxy path prefix when forwarding
648
- rewrite: (path4) => path4.replace(pathPrefixRegex, "")
649
- };
650
- proxyConfig["/_devtools"] = {
651
- target: `http://localhost:${port}`,
652
- changeOrigin: true
653
- };
654
- proxyConfig["/_api"] = {
655
- target: `http://localhost:${port}`,
656
- changeOrigin: true
657
- };
658
- proxyConfig["/_ws"] = {
659
- target: `http://localhost:${port}`,
660
- changeOrigin: true,
661
- ws: true
662
- };
663
- if (vite2.config.server) {
664
- vite2.config.server.proxy = proxyConfig;
665
- }
666
- }
667
- async function setupFileWatching() {
668
- if (!server || !vite) return;
669
- const debouncedHandlerReload = debounce(reloadHandlers, 100);
670
- const debouncedSeedReload = debounce(reloadSeeds, 100);
671
- const watchOpts = resolvedOptions;
672
- fileWatcher = await createFileWatcher({
673
- handlersDir: watchOpts.handlersDir,
674
- seedsDir: watchOpts.seedsDir,
675
- cwd,
676
- onHandlerChange: debouncedHandlerReload,
677
- onSeedChange: debouncedSeedReload
678
- });
679
- }
680
- async function reloadHandlers() {
681
- if (!server || !vite) return;
682
- try {
683
- const handlersResult = await loadHandlers(
684
- // biome-ignore lint/suspicious/noExplicitAny: v0.x compat — plugin.ts rewritten in Task 1.7
685
- resolvedOptions.handlersDir,
686
- vite,
687
- cwd
688
- );
689
- server.updateHandlers(handlersResult.handlers);
690
- server.wsHub.broadcast({
691
- type: "handlers:updated",
692
- data: { count: handlersResult.handlers.size }
693
- });
694
- printReloadNotification("handlers", handlersResult.handlers.size, resolvedOptions);
695
- } catch (error) {
696
- printError("Failed to reload handlers", error, resolvedOptions);
697
- }
698
- }
699
- async function reloadSeeds() {
700
- if (!server || !vite) return;
701
- try {
702
- const seedsResult = await loadSeeds(
703
- // biome-ignore lint/suspicious/noExplicitAny: v0.x compat — plugin.ts rewritten in Task 1.7
704
- resolvedOptions.seedsDir,
705
- vite,
706
- cwd
707
- );
708
- if (seedsResult.seeds.size > 0) {
709
- server.store.clearAll();
710
- await executeSeeds(seedsResult.seeds, server.store, server.document);
711
- } else {
712
- server.store.clearAll();
713
- }
714
- server.wsHub.broadcast({
715
- type: "seeds:updated",
716
- data: { count: seedsResult.seeds.size }
717
- });
718
- printReloadNotification("seeds", seedsResult.seeds.size, resolvedOptions);
719
- } catch (error) {
720
- printError("Failed to reload seeds", error, resolvedOptions);
721
- }
722
- }
723
- async function cleanup() {
724
- if (fileWatcher) {
725
- await fileWatcher.close();
726
- fileWatcher = null;
727
- }
728
- if (server) {
729
- await server.stop();
730
- server = null;
731
- }
732
- vite = null;
733
- }
734
- }
735
-
736
- // src/spec-id.ts
737
- function slugify(input) {
738
- return input.normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
739
- }
740
- function deriveSpecId(explicitId, document) {
741
- if (explicitId.trim()) {
742
- const id2 = slugify(explicitId);
743
- if (!id2) {
744
- throw new ValidationError(
745
- "SPEC_ID_MISSING",
746
- `Cannot derive spec ID: explicit id "${explicitId}" produces an empty slug. Please provide an id containing ASCII letters or numbers.`
747
- );
748
- }
749
- return id2;
750
- }
751
- const title = document.info?.title;
752
- if (!title || !title.trim()) {
753
- throw new ValidationError(
754
- "SPEC_ID_MISSING",
755
- "Cannot derive spec ID: info.title is missing from the OpenAPI document. Please set an explicit id in the spec configuration."
756
- );
757
- }
758
- const id = slugify(title);
759
- if (!id) {
760
- throw new ValidationError(
761
- "SPEC_ID_MISSING",
762
- `Cannot derive spec ID: info.title "${title}" produces an empty slug. Please set an explicit id in the spec configuration.`
763
- );
764
- }
765
- return id;
766
- }
767
- function validateUniqueIds(ids) {
768
- const seen = /* @__PURE__ */ new Set();
769
- const duplicates = /* @__PURE__ */ new Set();
770
- for (const id of ids) {
771
- if (seen.has(id)) {
772
- duplicates.add(id);
773
- }
774
- seen.add(id);
775
- }
776
- if (duplicates.size > 0) {
777
- const list = [...duplicates].join(", ");
778
- throw new ValidationError(
779
- "SPEC_ID_DUPLICATE",
780
- `Duplicate spec IDs: ${list}. Each spec must have a unique ID. Set explicit ids in spec configuration to resolve.`
781
- );
782
- }
783
- }
784
-
785
- // src/proxy-path.ts
786
- function deriveProxyPath(explicitPath, document, specId) {
787
- if (explicitPath.trim()) {
788
- return {
789
- proxyPath: normalizeProxyPath(explicitPath.trim(), specId),
790
- proxyPathSource: "explicit"
791
- };
792
- }
793
- const servers = document.servers;
794
- const serverUrl = servers?.[0]?.url?.trim();
795
- if (!serverUrl) {
796
- throw new ValidationError(
797
- "PROXY_PATH_MISSING",
798
- `[${specId}] Cannot derive proxyPath: no servers defined in the OpenAPI document. Set an explicit proxyPath in the spec configuration.`
799
- );
800
- }
801
- let path4;
802
- let parsedUrl;
803
- try {
804
- parsedUrl = new URL(serverUrl);
805
- } catch {
806
- }
807
- if (parsedUrl) {
808
- try {
809
- path4 = decodeURIComponent(parsedUrl.pathname);
810
- } catch {
811
- path4 = parsedUrl.pathname;
812
- }
813
- } else {
814
- path4 = serverUrl;
815
- }
816
- return {
817
- proxyPath: normalizeProxyPath(path4, specId),
818
- proxyPathSource: "auto"
819
- };
820
- }
821
- function normalizeProxyPath(path4, specId) {
822
- path4 = path4.trim();
823
- const queryIdx = path4.indexOf("?");
824
- const hashIdx = path4.indexOf("#");
825
- const cutIdx = Math.min(
826
- queryIdx >= 0 ? queryIdx : path4.length,
827
- hashIdx >= 0 ? hashIdx : path4.length
828
- );
829
- let normalized = path4.slice(0, cutIdx);
830
- normalized = normalized.startsWith("/") ? normalized : `/${normalized}`;
831
- normalized = normalized.replace(/\/{2,}/g, "/");
832
- const segments = normalized.split("/");
833
- const resolved = [];
834
- for (const segment of segments) {
835
- if (segment === ".") {
836
- continue;
837
- }
838
- if (segment === "..") {
839
- if (resolved.length > 1) {
840
- resolved.pop();
841
- }
842
- continue;
843
- }
844
- resolved.push(segment);
845
- }
846
- normalized = resolved.join("/") || "/";
847
- if (normalized.length > 1 && normalized.endsWith("/")) {
848
- normalized = normalized.slice(0, -1);
849
- }
850
- if (normalized === "/") {
851
- throw new ValidationError(
852
- "PROXY_PATH_TOO_BROAD",
853
- `[${specId}] proxyPath "/" is too broad \u2014 it would capture all requests. Set a more specific proxyPath (e.g., "/api/v1").`
854
- );
855
- }
856
- return normalized;
857
- }
858
- function validateUniqueProxyPaths(specs) {
859
- const paths = /* @__PURE__ */ new Map();
860
- for (const spec of specs) {
861
- const path4 = spec.proxyPath?.trim();
862
- if (!path4) {
863
- continue;
864
- }
865
- if (paths.has(path4)) {
866
- throw new ValidationError(
867
- "PROXY_PATH_DUPLICATE",
868
- `Duplicate proxyPath "${path4}" used by specs "${paths.get(path4)}" and "${spec.id}". Each spec must have a unique proxyPath.`
869
- );
870
- }
871
- paths.set(path4, spec.id);
872
- }
873
- const sortedPaths = Array.from(paths.entries()).sort(([a], [b]) => a.length - b.length);
874
- for (let i = 0; i < sortedPaths.length; i++) {
875
- for (let j = i + 1; j < sortedPaths.length; j++) {
876
- const [shorter, shorterId] = sortedPaths[i];
877
- const [longer, longerId] = sortedPaths[j];
878
- if (longer.startsWith(`${shorter}/`)) {
879
- throw new ValidationError(
880
- "PROXY_PATH_OVERLAP",
881
- `Overlapping proxyPaths: "${shorter}" (${shorterId}) is a prefix of "${longer}" (${longerId}). This would cause routing ambiguity.`
882
- );
883
- }
670
+ const id = slugify(title);
671
+ if (!id) {
672
+ throw new ValidationError(
673
+ "SPEC_ID_MISSING",
674
+ `Cannot derive spec ID: info.title "${title}" produces an empty slug. Please set an explicit id in the spec configuration.`
675
+ );
676
+ }
677
+ return id;
678
+ }
679
+ function validateUniqueIds(ids) {
680
+ const seen = /* @__PURE__ */ new Set();
681
+ const duplicates = /* @__PURE__ */ new Set();
682
+ for (const id of ids) {
683
+ if (seen.has(id)) {
684
+ duplicates.add(id);
884
685
  }
686
+ seen.add(id);
687
+ }
688
+ if (duplicates.size > 0) {
689
+ const list = [...duplicates].join(", ");
690
+ throw new ValidationError(
691
+ "SPEC_ID_DUPLICATE",
692
+ `Duplicate spec IDs: ${list}. Each spec must have a unique ID. Set explicit ids in spec configuration to resolve.`
693
+ );
885
694
  }
886
695
  }
696
+
697
+ // src/orchestrator.ts
887
698
  var SPEC_COLORS = [
888
699
  "#4ade80",
889
700
  // green
@@ -942,7 +753,7 @@ async function processSpec(specConfig, index, options, vite, cwd, logger) {
942
753
  }
943
754
  function buildCorsConfig(options) {
944
755
  const isWildcardOrigin = options.corsOrigin === "*" || Array.isArray(options.corsOrigin) && options.corsOrigin.includes("*");
945
- const effectiveCorsOrigin = Array.isArray(options.corsOrigin) && options.corsOrigin.includes("*") ? "*" : options.corsOrigin;
756
+ const effectiveCorsOrigin = isWildcardOrigin ? "*" : options.corsOrigin;
946
757
  return {
947
758
  origin: effectiveCorsOrigin,
948
759
  allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
@@ -999,12 +810,11 @@ async function createOrchestrator(options, vite, cwd) {
999
810
  cwd,
1000
811
  logger
1001
812
  );
1002
- const specConfig = options.specs[i];
1003
- specConfig.id = resolvedConfig.id;
1004
- specConfig.proxyPath = resolvedConfig.proxyPath;
1005
- specConfig.proxyPathSource = resolvedConfig.proxyPathSource;
1006
- specConfig.handlersDir = resolvedConfig.handlersDir;
1007
- specConfig.seedsDir = resolvedConfig.seedsDir;
813
+ instance.config.id = resolvedConfig.id;
814
+ instance.config.proxyPath = resolvedConfig.proxyPath;
815
+ instance.config.proxyPathSource = resolvedConfig.proxyPathSource;
816
+ instance.config.handlersDir = resolvedConfig.handlersDir;
817
+ instance.config.seedsDir = resolvedConfig.seedsDir;
1008
818
  instances.push(instance);
1009
819
  }
1010
820
  validateUniqueIds(instances.map((inst) => inst.id));
@@ -1025,12 +835,16 @@ async function createOrchestrator(options, vite, cwd) {
1025
835
  if (options.devtools) {
1026
836
  mountDevToolsSpa(mainApp, logger);
1027
837
  }
838
+ if (instances.length > 1) {
839
+ logger.warn?.(
840
+ "[vite-plugin-open-api-server] Only first spec's internal API mounted on /_api; multi-spec support planned in Epic 3 (Task 3.x)."
841
+ );
842
+ mainApp.use("/_api/*", async (c, next) => {
843
+ await next();
844
+ c.header("X-Multi-Spec-Warning", `Only showing data for spec "${instances[0].id}"`);
845
+ });
846
+ }
1028
847
  if (instances.length > 0) {
1029
- if (instances.length > 1) {
1030
- logger.warn?.(
1031
- "[vite-plugin-open-api-server] Only first spec's internal API mounted on /_api; multi-spec support planned in Epic 3 (Task 3.x)."
1032
- );
1033
- }
1034
848
  const firstInstance = instances[0];
1035
849
  mountInternalApi(mainApp, {
1036
850
  store: firstInstance.server.store,
@@ -1048,6 +862,37 @@ async function createOrchestrator(options, vite, cwd) {
1048
862
  const specsInfo = instances.map((inst) => inst.info);
1049
863
  let serverInstance = null;
1050
864
  let boundPort = 0;
865
+ async function startServerOnPort(fetchHandler, port) {
866
+ let createAdaptorServer;
867
+ try {
868
+ const nodeServer = await import('@hono/node-server');
869
+ createAdaptorServer = nodeServer.createAdaptorServer;
870
+ } catch {
871
+ throw new Error("@hono/node-server is required. Install with: npm install @hono/node-server");
872
+ }
873
+ const server = createAdaptorServer({ fetch: fetchHandler });
874
+ const actualPort = await new Promise((resolve, reject) => {
875
+ const onListening = () => {
876
+ server.removeListener("error", onError);
877
+ const addr = server.address();
878
+ resolve(typeof addr === "object" && addr ? addr.port : port);
879
+ };
880
+ const onError = (err) => {
881
+ server.removeListener("listening", onListening);
882
+ server.close(() => {
883
+ });
884
+ if (err.code === "EADDRINUSE") {
885
+ reject(new Error(`[vite-plugin-open-api-server] Port ${port} is already in use.`));
886
+ } else {
887
+ reject(new Error(`[vite-plugin-open-api-server] Server error: ${err.message}`));
888
+ }
889
+ };
890
+ server.once("listening", onListening);
891
+ server.once("error", onError);
892
+ server.listen(port);
893
+ });
894
+ return { server, actualPort };
895
+ }
1051
896
  return {
1052
897
  app: mainApp,
1053
898
  instances,
@@ -1059,51 +904,18 @@ async function createOrchestrator(options, vite, cwd) {
1059
904
  if (serverInstance) {
1060
905
  throw new Error("[vite-plugin-open-api-server] Server already running. Call stop() first.");
1061
906
  }
1062
- let createAdaptorServer;
1063
- try {
1064
- const nodeServer = await import('@hono/node-server');
1065
- createAdaptorServer = nodeServer.createAdaptorServer;
1066
- } catch {
1067
- throw new Error(
1068
- "@hono/node-server is required. Install with: npm install @hono/node-server"
1069
- );
1070
- }
1071
- const server = createAdaptorServer({ fetch: mainApp.fetch });
1072
- await new Promise((resolve, reject) => {
1073
- const onListening = () => {
1074
- server.removeListener("error", onError);
1075
- const addr = server.address();
1076
- const actualPort = typeof addr === "object" && addr ? addr.port : options.port;
1077
- logger.info(
1078
- `[vite-plugin-open-api-server] Server started on http://localhost:${actualPort}`
1079
- );
1080
- boundPort = actualPort;
1081
- serverInstance = server;
1082
- resolve();
1083
- };
1084
- const onError = (err) => {
1085
- server.removeListener("listening", onListening);
1086
- server.close(() => {
1087
- });
1088
- serverInstance = null;
1089
- boundPort = 0;
1090
- if (err.code === "EADDRINUSE") {
1091
- reject(
1092
- new Error(`[vite-plugin-open-api-server] Port ${options.port} is already in use.`)
1093
- );
1094
- } else {
1095
- reject(new Error(`[vite-plugin-open-api-server] Server error: ${err.message}`));
1096
- }
1097
- };
1098
- server.once("listening", onListening);
1099
- server.once("error", onError);
1100
- server.listen(options.port);
1101
- });
907
+ const { server, actualPort } = await startServerOnPort(mainApp.fetch, options.port);
908
+ serverInstance = server;
909
+ boundPort = actualPort;
910
+ logger.info(`[vite-plugin-open-api-server] Server started on http://localhost:${actualPort}`);
1102
911
  },
1103
912
  async stop() {
1104
913
  const server = serverInstance;
1105
914
  if (server) {
1106
915
  try {
916
+ if (typeof server.closeAllConnections === "function") {
917
+ server.closeAllConnections();
918
+ }
1107
919
  await new Promise((resolve, reject) => {
1108
920
  server.close((err) => {
1109
921
  if (err) {
@@ -1126,6 +938,229 @@ async function createOrchestrator(options, vite, cwd) {
1126
938
  };
1127
939
  }
1128
940
 
941
+ // src/plugin.ts
942
+ var VIRTUAL_DEVTOOLS_TAB_ID = "virtual:open-api-devtools-tab";
943
+ var RESOLVED_VIRTUAL_DEVTOOLS_TAB_ID = `\0${VIRTUAL_DEVTOOLS_TAB_ID}`;
944
+ function openApiServer(options) {
945
+ const resolvedOptions = resolveOptions(options);
946
+ let orchestrator = null;
947
+ let fileWatchers = [];
948
+ let cwd = process.cwd();
949
+ return {
950
+ name: "vite-plugin-open-api-server",
951
+ apply: "serve",
952
+ /**
953
+ * Ensure @vue/devtools-api is available for the DevTools tab module
954
+ *
955
+ * The virtual module imports from '@vue/devtools-api', which Vite needs
956
+ * to pre-bundle so it can be resolved at runtime in the browser, even
957
+ * though the host app may not have it as a direct dependency.
958
+ */
959
+ config() {
960
+ if (!resolvedOptions.devtools || !resolvedOptions.enabled) {
961
+ return;
962
+ }
963
+ const require2 = createRequire(import.meta.url);
964
+ try {
965
+ require2.resolve("@vue/devtools-api");
966
+ } catch {
967
+ return;
968
+ }
969
+ return {
970
+ optimizeDeps: {
971
+ include: ["@vue/devtools-api"]
972
+ }
973
+ };
974
+ },
975
+ /**
976
+ * Resolve the virtual module for DevTools tab registration
977
+ */
978
+ resolveId(id) {
979
+ if (id === VIRTUAL_DEVTOOLS_TAB_ID) {
980
+ return RESOLVED_VIRTUAL_DEVTOOLS_TAB_ID;
981
+ }
982
+ },
983
+ /**
984
+ * Load the virtual module that registers the custom Vue DevTools tab
985
+ *
986
+ * This code is served as a proper Vite module, allowing bare import
987
+ * specifiers to be resolved through Vite's dependency pre-bundling.
988
+ */
989
+ load(id) {
990
+ if (id === RESOLVED_VIRTUAL_DEVTOOLS_TAB_ID) {
991
+ return `
992
+ import { addCustomTab } from '@vue/devtools-api';
993
+
994
+ try {
995
+ // Route through Vite's proxy so it works in all environments
996
+ const iframeSrc = window.location.origin + '${DEVTOOLS_PROXY_PATH}/';
997
+
998
+ addCustomTab({
999
+ name: 'vite-plugin-open-api-server',
1000
+ title: 'OpenAPI Server',
1001
+ icon: 'https://api.iconify.design/carbon:api-1.svg?width=24&height=24&color=%2394a3b8',
1002
+ view: {
1003
+ type: 'iframe',
1004
+ src: iframeSrc,
1005
+ },
1006
+ category: 'app',
1007
+ });
1008
+ } catch (e) {
1009
+ // @vue/devtools-api not available - expected when the package is not installed
1010
+ }
1011
+ `;
1012
+ }
1013
+ },
1014
+ /**
1015
+ * Configure the Vite dev server
1016
+ *
1017
+ * This hook is called when the dev server is created.
1018
+ * We use it to:
1019
+ * 1. Create and start the multi-spec orchestrator
1020
+ * 2. Configure Vite's multi-proxy for all specs
1021
+ * 3. Set up per-spec file watchers for hot reload
1022
+ * 4. Print startup banner
1023
+ */
1024
+ async configureServer(viteServer) {
1025
+ cwd = viteServer.config.root;
1026
+ if (!resolvedOptions.enabled) {
1027
+ return;
1028
+ }
1029
+ try {
1030
+ orchestrator = await createOrchestrator(resolvedOptions, viteServer, cwd);
1031
+ await orchestrator.start();
1032
+ configureMultiProxy(viteServer, orchestrator.instances, orchestrator.port);
1033
+ fileWatchers = await setupPerSpecFileWatching(orchestrator, viteServer, cwd);
1034
+ if (!resolvedOptions.silent && orchestrator.instances.length > 0) {
1035
+ const firstInstance = orchestrator.instances[0];
1036
+ const bannerInfo = extractBannerInfo(
1037
+ firstInstance.server.registry,
1038
+ {
1039
+ info: {
1040
+ title: firstInstance.server.document.info?.title ?? "OpenAPI Server",
1041
+ version: firstInstance.server.document.info?.version ?? "1.0.0"
1042
+ }
1043
+ },
1044
+ // Handler/seed counts are not tracked per-instance yet.
1045
+ // Multi-spec banner (Epic 5, Task 5.1) will display proper per-spec counts.
1046
+ 0,
1047
+ 0,
1048
+ resolvedOptions
1049
+ );
1050
+ printBanner(bannerInfo, resolvedOptions);
1051
+ }
1052
+ } catch (error) {
1053
+ await teardown();
1054
+ printError("Failed to start OpenAPI mock server", error, resolvedOptions);
1055
+ throw error;
1056
+ }
1057
+ },
1058
+ /**
1059
+ * Inject Vue DevTools custom tab registration script
1060
+ *
1061
+ * When devtools is enabled, this injects a script tag that loads the
1062
+ * virtual module for custom tab registration. Using a virtual module
1063
+ * (instead of an inline script) ensures that bare import specifiers
1064
+ * like `@vue/devtools-api` are resolved through Vite's module pipeline.
1065
+ */
1066
+ transformIndexHtml() {
1067
+ if (!resolvedOptions.devtools || !resolvedOptions.enabled) {
1068
+ return;
1069
+ }
1070
+ return [
1071
+ {
1072
+ tag: "script",
1073
+ attrs: { type: "module", src: `/@id/${VIRTUAL_DEVTOOLS_TAB_ID}` },
1074
+ injectTo: "head"
1075
+ }
1076
+ ];
1077
+ },
1078
+ /**
1079
+ * Clean up when Vite server closes
1080
+ *
1081
+ * NOTE: closeBundle() is called by Vite when the dev server shuts down
1082
+ * (e.g., Ctrl+C). This is the same lifecycle hook used in v0.x.
1083
+ * While configureServer's viteServer.httpServer?.on('close', ...) is
1084
+ * an alternative, closeBundle() is more reliable across Vite versions
1085
+ * and is the established pattern in this codebase.
1086
+ */
1087
+ async closeBundle() {
1088
+ await teardown();
1089
+ }
1090
+ };
1091
+ async function teardown() {
1092
+ await Promise.allSettled(fileWatchers.map((w) => w.close()));
1093
+ fileWatchers = [];
1094
+ if (orchestrator) {
1095
+ try {
1096
+ await orchestrator.stop();
1097
+ } catch {
1098
+ }
1099
+ orchestrator = null;
1100
+ }
1101
+ }
1102
+ async function setupPerSpecFileWatching(orch, viteServer, projectCwd) {
1103
+ const watchers = [];
1104
+ try {
1105
+ for (const instance of orch.instances) {
1106
+ const specServer = instance.server;
1107
+ const specConfig = instance.config;
1108
+ const debouncedHandlerReload = debounce(
1109
+ () => reloadSpecHandlers(specServer, specConfig.handlersDir, viteServer, projectCwd),
1110
+ 100
1111
+ );
1112
+ const debouncedSeedReload = debounce(
1113
+ () => reloadSpecSeeds(specServer, specConfig.seedsDir, viteServer, projectCwd),
1114
+ 100
1115
+ );
1116
+ const watcher = await createFileWatcher({
1117
+ handlersDir: specConfig.handlersDir,
1118
+ seedsDir: specConfig.seedsDir,
1119
+ cwd: projectCwd,
1120
+ onHandlerChange: debouncedHandlerReload,
1121
+ onSeedChange: debouncedSeedReload
1122
+ });
1123
+ watchers.push(watcher);
1124
+ }
1125
+ } catch (error) {
1126
+ await Promise.allSettled(watchers.map((w) => w.close()));
1127
+ throw error;
1128
+ }
1129
+ return watchers;
1130
+ }
1131
+ async function reloadSpecHandlers(server, handlersDir, viteServer, projectCwd) {
1132
+ try {
1133
+ const logger = resolvedOptions.logger ?? console;
1134
+ const handlersResult = await loadHandlers(handlersDir, viteServer, projectCwd, logger);
1135
+ server.updateHandlers(handlersResult.handlers);
1136
+ server.wsHub.broadcast({
1137
+ type: "handlers:updated",
1138
+ data: { count: handlersResult.handlers.size }
1139
+ });
1140
+ printReloadNotification("handlers", handlersResult.handlers.size, resolvedOptions);
1141
+ } catch (error) {
1142
+ printError("Failed to reload handlers", error, resolvedOptions);
1143
+ }
1144
+ }
1145
+ async function reloadSpecSeeds(server, seedsDir, viteServer, projectCwd) {
1146
+ try {
1147
+ const logger = resolvedOptions.logger ?? console;
1148
+ const seedsResult = await loadSeeds(seedsDir, viteServer, projectCwd, logger);
1149
+ server.store.clearAll();
1150
+ if (seedsResult.seeds.size > 0) {
1151
+ await executeSeeds(seedsResult.seeds, server.store, server.document);
1152
+ }
1153
+ server.wsHub.broadcast({
1154
+ type: "seeds:updated",
1155
+ data: { count: seedsResult.seeds.size }
1156
+ });
1157
+ printReloadNotification("seeds", seedsResult.seeds.size, resolvedOptions);
1158
+ } catch (error) {
1159
+ printError("Failed to reload seeds", error, resolvedOptions);
1160
+ }
1161
+ }
1162
+ }
1163
+
1129
1164
  // src/devtools.ts
1130
1165
  async function registerDevTools(app, options = {}) {
1131
1166
  const { enabled = true, label = "OpenAPI Server", port, host, protocol } = options;
@@ -1156,12 +1191,12 @@ async function registerDevTools(app, options = {}) {
1156
1191
  console.warn("[OpenAPI DevTools] Failed to register with Vue DevTools:", error);
1157
1192
  }
1158
1193
  }
1159
- function getDevToolsUrl(port = 3e3, host, protocol) {
1194
+ function getDevToolsUrl(port = 4e3, host, protocol) {
1160
1195
  const actualProtocol = protocol || (typeof window !== "undefined" ? window.location.protocol.replace(":", "") : "http");
1161
1196
  const actualHost = host || (typeof window !== "undefined" ? window.location.hostname : "localhost");
1162
1197
  return `${actualProtocol}://${actualHost}:${port}/_devtools/`;
1163
1198
  }
1164
1199
 
1165
- export { SPEC_COLORS, ValidationError, createFileWatcher, createOrchestrator, debounce, deriveProxyPath, deriveSpecId, getDevToolsUrl, getHandlerFiles, getSeedFiles, loadHandlers, loadSeeds, normalizeProxyPath, openApiServer, registerDevTools, resolveOptions, slugify, validateSpecs, validateUniqueIds, validateUniqueProxyPaths };
1200
+ export { API_PROXY_PATH, DEVTOOLS_PROXY_PATH, SPEC_COLORS, ValidationError, WS_PROXY_PATH, configureMultiProxy, createFileWatcher, createOrchestrator, debounce, deriveProxyPath, deriveSpecId, getDevToolsUrl, getHandlerFiles, getSeedFiles, loadHandlers, loadSeeds, normalizeProxyPath, openApiServer, registerDevTools, resolveOptions, slugify, validateSpecs, validateUniqueIds, validateUniqueProxyPaths };
1166
1201
  //# sourceMappingURL=index.js.map
1167
1202
  //# sourceMappingURL=index.js.map