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