@angelitosystems/nest-devtools 1.0.13 → 1.0.15

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
@@ -591,12 +591,28 @@ var PerformanceInstrumentation = class {
591
591
  };
592
592
 
593
593
  // src/instrumentation/app-explorer.ts
594
+ import { createRequire } from "module";
595
+ var safeRequire = (() => {
596
+ try {
597
+ return createRequire(import.meta.url);
598
+ } catch {
599
+ return null;
600
+ }
601
+ })();
602
+ var PATH_METADATA = "path";
603
+ var METHOD_METADATA = "method";
604
+ var GUARDS_METADATA = "__guards__";
605
+ var INTERCEPTORS_METADATA = "__interceptors__";
606
+ var PIPES_METADATA = "__pipes__";
607
+ var EXCEPTION_FILTERS_METADATA = "__exceptionFilters__";
608
+ var ROUTE_ARGS_METADATA = "__routeArguments__";
609
+ var BODY_PARAM_TYPE = 3;
610
+ var HTTP_METHOD_NAMES = ["GET", "POST", "PUT", "DELETE", "PATCH", "ALL", "OPTIONS", "HEAD"];
594
611
  var AppExplorer = class {
595
612
  constructor(ctx) {
596
613
  this.ctx = ctx;
597
614
  }
598
615
  ctx;
599
- /** Walk the container and emit app.snapshot. Returns a no-op cleanup. */
600
616
  attach(app) {
601
617
  try {
602
618
  const snapshot = this.buildSnapshot(app);
@@ -608,12 +624,13 @@ var AppExplorer = class {
608
624
  }
609
625
  buildSnapshot(app) {
610
626
  const internal = app.container;
627
+ const globals = this.extractGlobals(app);
611
628
  const modules = [];
612
629
  const modulesMap = internal?.getModules?.();
613
630
  if (modulesMap) {
614
631
  for (const [id, node] of modulesMap) {
615
632
  const moduleName = node.metatype?.name ?? String(id);
616
- const members = this.extractMembers(node);
633
+ const members = this.extractMembers(node, globals);
617
634
  modules.push({
618
635
  name: moduleName,
619
636
  imports: [],
@@ -628,23 +645,55 @@ var AppExplorer = class {
628
645
  projectName: this.ctx.projectInfo.projectName,
629
646
  modules,
630
647
  nestjsVersion: this.ctx.projectInfo.nestjsVersion,
631
- capturedAt: Date.now()
648
+ capturedAt: Date.now(),
649
+ globals
632
650
  };
633
651
  }
634
- extractMembers(node) {
652
+ /** Global prefix, versioning, and APP_GUARD/APP_INTERCEPTOR/APP_FILTER/APP_PIPE providers (from AppModule's `providers` array). */
653
+ extractGlobals(app) {
654
+ const result = { prefix: null, versioning: null, guards: [], interceptors: [], filters: [], pipes: [] };
655
+ try {
656
+ const config = app.config;
657
+ result.prefix = typeof config?.getGlobalPrefix === "function" ? config.getGlobalPrefix() || null : null;
658
+ const versioning = typeof config?.getVersioning === "function" ? config.getVersioning() : null;
659
+ if (versioning) result.versioning = { type: String(versioning.type), defaultVersion: versioning.defaultVersion };
660
+ const internal = app.container;
661
+ const modulesMap = internal?.getModules?.();
662
+ if (modulesMap) {
663
+ for (const node of modulesMap.values()) {
664
+ const providersMap = node.providers;
665
+ if (!(providersMap instanceof Map)) continue;
666
+ for (const wrapper of providersMap.values()) {
667
+ const w = wrapper;
668
+ const name = w?.metatype?.name ?? w?.name;
669
+ if (!name || typeof name !== "string") continue;
670
+ const instance = w?.instance;
671
+ if (!instance) continue;
672
+ if (typeof instance.canActivate === "function" && name.endsWith("Guard")) pushUnique(result.guards, name);
673
+ else if (typeof instance.intercept === "function" && name.endsWith("Interceptor")) pushUnique(result.interceptors, name);
674
+ else if (typeof instance.catch === "function" && name.endsWith("Filter")) pushUnique(result.filters, name);
675
+ else if (typeof instance.transform === "function" && name.endsWith("Pipe")) pushUnique(result.pipes, name);
676
+ }
677
+ }
678
+ }
679
+ } catch {
680
+ }
681
+ return result;
682
+ }
683
+ extractMembers(node, globals) {
635
684
  const controllers = [];
636
685
  const providers = [];
637
686
  const controllersMap = node.controllers;
638
687
  if (controllersMap instanceof Map) {
639
688
  for (const [id, wrapper] of controllersMap) {
640
- const member = this.memberFromWrapper(id, wrapper, "controller");
689
+ const member = this.memberFromWrapper(id, wrapper, globals, "controller");
641
690
  if (member) controllers.push(member);
642
691
  }
643
692
  }
644
693
  const providersMap = node.providers;
645
694
  if (providersMap instanceof Map) {
646
695
  for (const [id, wrapper] of providersMap) {
647
- const member = this.memberFromWrapper(id, wrapper);
696
+ const member = this.memberFromWrapper(id, wrapper, globals);
648
697
  if (!member) continue;
649
698
  if (wrapper && wrapper.subtype === "httpController") {
650
699
  controllers.push(member);
@@ -655,73 +704,144 @@ var AppExplorer = class {
655
704
  }
656
705
  return { controllers, providers };
657
706
  }
658
- memberFromWrapper(id, wrapper, forcedType) {
707
+ memberFromWrapper(id, wrapper, globals, forcedType) {
659
708
  const w = wrapper;
660
709
  const name = w?.metatype?.name ?? (typeof id === "string" ? id.replace(/^[A-Z_0-9]+:/, "") : "unknown");
661
710
  const type = forcedType ?? classify(name, w?.instance);
662
- const routes = type === "controller" ? extractControllerRoutes(w?.metatype) : void 0;
663
- return { name, type, routes };
711
+ if (type !== "controller" || !w?.metatype) {
712
+ return { name, type };
713
+ }
714
+ const { basePath, routes } = this.extractRoutes(w.metatype, globals);
715
+ return {
716
+ name,
717
+ type,
718
+ basePath,
719
+ routeDetails: routes,
720
+ routes: routes.map((r) => `${r.method} ${r.path}`)
721
+ // legacy field, kept for old dashboards
722
+ };
664
723
  }
665
- };
666
- function classify(name, instance) {
667
- if (name.endsWith("Gateway")) return "gateway";
668
- if (name.endsWith("Guard")) return "guard";
669
- if (name.endsWith("Interceptor")) return "interceptor";
670
- if (name.endsWith("Pipe")) return "pipe";
671
- if (name.endsWith("Filter")) return "filter";
672
- if (instance && typeof instance.canActivate === "function") return "guard";
673
- if (instance && typeof instance.intercept === "function") return "interceptor";
674
- if (instance && typeof instance.transform === "function") return "pipe";
675
- if (instance && typeof instance.catch === "function") return "filter";
676
- return "provider";
677
- }
678
- function extractControllerRoutes(metatype) {
679
- if (!metatype) return [];
680
- try {
681
- const proto = metatype.prototype;
682
- if (!proto) return [];
683
- const props = Object.getOwnPropertyNames(proto).filter((name) => name !== "constructor");
724
+ /** Extract every HTTP route of a controller class, with method/path/guards/interceptors/pipes/filters/dto. */
725
+ extractRoutes(controller, globals) {
684
726
  const routes = [];
685
- for (const key of props) {
686
- const desc = Object.getOwnPropertyDescriptor(proto, key);
687
- if (!desc?.value) continue;
688
- const value = desc.value;
689
- const routesForMethod = gatherHttpRoutes(value);
690
- for (const r of routesForMethod) {
691
- if (typeof r === "string" && r.length > 0) routes.push(r);
727
+ let basePath = "";
728
+ try {
729
+ const R = Reflect;
730
+ basePath = normalizeSegment(R.getMetadata?.(PATH_METADATA, controller) ?? "");
731
+ const classGuards = readMetaNames(R, GUARDS_METADATA, controller);
732
+ const classInterceptors = readMetaNames(R, INTERCEPTORS_METADATA, controller);
733
+ const classPipes = readMetaNames(R, PIPES_METADATA, controller);
734
+ const classFilters = readMetaNames(R, EXCEPTION_FILTERS_METADATA, controller);
735
+ const proto = controller.prototype;
736
+ if (!proto) return { basePath, routes };
737
+ const version = globals.versioning?.defaultVersion;
738
+ const versionSegment = globals.versioning?.type === "URI" && version && version !== "-1" ? `v${Array.isArray(version) ? version[0] : version}` : "";
739
+ for (const key of Object.getOwnPropertyNames(proto)) {
740
+ if (key === "constructor") continue;
741
+ const handler = proto[key];
742
+ if (typeof handler !== "function") continue;
743
+ const httpMethod = R.getMetadata?.(METHOD_METADATA, handler);
744
+ if (httpMethod === void 0) continue;
745
+ const methodPath = normalizeSegment(R.getMetadata?.(PATH_METADATA, handler) ?? "");
746
+ const fullPath = ["/", globals.prefix, versionSegment, basePath, methodPath].filter((segment) => segment && segment !== "/").join("/").replace(/\/+/g, "/");
747
+ const methodGuards = readMetaNames(R, GUARDS_METADATA, handler);
748
+ const methodInterceptors = readMetaNames(R, INTERCEPTORS_METADATA, handler);
749
+ const methodPipes = readMetaNames(R, PIPES_METADATA, handler);
750
+ const methodFilters = readMetaNames(R, EXCEPTION_FILTERS_METADATA, handler);
751
+ routes.push({
752
+ method: HTTP_METHOD_NAMES[httpMethod] ?? "GET",
753
+ path: fullPath.startsWith("/") ? fullPath : `/${fullPath}`,
754
+ handlerName: key,
755
+ guards: dedupe([...classGuards, ...methodGuards]),
756
+ interceptors: dedupe([...classInterceptors, ...methodInterceptors]),
757
+ pipes: dedupe([...classPipes, ...methodPipes]),
758
+ filters: dedupe([...classFilters, ...methodFilters]),
759
+ dto: this.extractBodyDto(R, controller, proto, key)
760
+ });
692
761
  }
762
+ } catch {
693
763
  }
694
- return routes;
695
- } catch {
696
- return [];
764
+ return { basePath, routes };
697
765
  }
698
- }
699
- function gatherHttpRoutes(value) {
700
- try {
701
- const decorators = Reflect.getMetadata?.("design:http:method", {}, void 0, void 0) ?? [];
702
- const out = [];
703
- if (decorators.length > 0) {
704
- for (const d of decorators) {
705
- if (d && typeof d === "object" && d.method && d.path) {
706
- out.push(`${String(d.method).toUpperCase()} ${String(d.path)}`);
766
+ /** Best-effort: find the @Body() parameter's class and describe its fields via class-validator, if present. */
767
+ extractBodyDto(R, controller, proto, methodName) {
768
+ try {
769
+ const paramTypes = R.getMetadata?.("design:paramtypes", proto, methodName) ?? [];
770
+ const routeArgs = R.getMetadata?.(ROUTE_ARGS_METADATA, controller, methodName) ?? {};
771
+ let bodyIndex = -1;
772
+ for (const key of Object.keys(routeArgs)) {
773
+ const [paramType] = key.split(":");
774
+ if (Number(paramType) === BODY_PARAM_TYPE) {
775
+ bodyIndex = routeArgs[key]?.index ?? -1;
776
+ break;
707
777
  }
708
778
  }
779
+ if (bodyIndex === -1) return null;
780
+ const dtoClass = paramTypes[bodyIndex];
781
+ if (!dtoClass || !dtoClass.name || ["Object", "String", "Number", "Boolean", "Array"].includes(dtoClass.name)) {
782
+ return null;
783
+ }
784
+ return { name: dtoClass.name, fields: this.describeDtoFields(dtoClass) };
785
+ } catch {
786
+ return null;
709
787
  }
710
- if (typeof value === "function") {
711
- const fn = value;
712
- const path = fn.PATH ?? void 0;
713
- const method = fn.METHOD ?? void 0;
714
- if (typeof path === "string" && typeof method === "string") {
715
- out.push(`${method.toUpperCase()} ${path}`);
716
- } else if (typeof path === "string") {
717
- out.push(path);
788
+ }
789
+ /** Reads class-validator's metadata storage if the package is installed in the host app. Falls back to []. */
790
+ describeDtoFields(dtoClass) {
791
+ try {
792
+ if (!safeRequire) return [];
793
+ const cv = safeRequire("class-validator");
794
+ const storage = cv.getMetadataStorage?.();
795
+ const metas = storage?.getTargetValidationMetadatas?.(dtoClass, "", true, false) ?? [];
796
+ const byProperty = /* @__PURE__ */ new Map();
797
+ for (const meta of metas) {
798
+ const entry = byProperty.get(meta.propertyName) ?? { types: [], optional: false };
799
+ if (meta.name === "isOptional") entry.optional = true;
800
+ if (meta.name) entry.types.push(meta.name);
801
+ byProperty.set(meta.propertyName, entry);
718
802
  }
803
+ return [...byProperty.entries()].map(([name, info]) => ({
804
+ name,
805
+ type: info.types.find((t) => /^is[A-Z]/.test(t))?.replace(/^is/, "") ?? "unknown",
806
+ optional: info.optional,
807
+ rules: dedupe(info.types)
808
+ }));
809
+ } catch {
810
+ return [];
719
811
  }
720
- return out;
812
+ }
813
+ };
814
+ function readMetaNames(R, key, target) {
815
+ try {
816
+ const value = R.getMetadata?.(key, target);
817
+ if (!Array.isArray(value)) return [];
818
+ return value.map((fn) => fn?.name).filter((name) => Boolean(name));
721
819
  } catch {
722
820
  return [];
723
821
  }
724
822
  }
823
+ function normalizeSegment(segment) {
824
+ const s = Array.isArray(segment) ? segment[0] ?? "" : segment;
825
+ return String(s ?? "").replace(/^\/+|\/+$/g, "");
826
+ }
827
+ function dedupe(items) {
828
+ return [...new Set(items)];
829
+ }
830
+ function pushUnique(list, value) {
831
+ if (!list.includes(value)) list.push(value);
832
+ }
833
+ function classify(name, instance) {
834
+ if (name.endsWith("Gateway")) return "gateway";
835
+ if (name.endsWith("Guard")) return "guard";
836
+ if (name.endsWith("Interceptor")) return "interceptor";
837
+ if (name.endsWith("Pipe")) return "pipe";
838
+ if (name.endsWith("Filter")) return "filter";
839
+ if (instance && typeof instance.canActivate === "function") return "guard";
840
+ if (instance && typeof instance.intercept === "function") return "interceptor";
841
+ if (instance && typeof instance.transform === "function") return "pipe";
842
+ if (instance && typeof instance.catch === "function") return "filter";
843
+ return "provider";
844
+ }
725
845
 
726
846
  // src/instrumentation/websockets.ts
727
847
  import { requestContext as requestContext5 } from "@angelitosystems/devtools-core";
@@ -1398,7 +1518,7 @@ function detectNestJsVersion() {
1398
1518
  }
1399
1519
 
1400
1520
  // src/version.ts
1401
- var SDK_VERSION = "1.0.13";
1521
+ var SDK_VERSION = "1.0.15";
1402
1522
 
1403
1523
  // src/banner.ts
1404
1524
  var RESET = "\x1B[0m";