@atlaspack/inspector 0.0.19-canary.3998

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 (235) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +126 -0
  3. package/bin/atlaspack-inspector.js +2 -0
  4. package/lib/backend/cli.js +4 -0
  5. package/lib/backend/config/logger.js +16 -0
  6. package/lib/backend/config/middleware/cacheDataMiddleware.js +60 -0
  7. package/lib/backend/config/middleware/errorHandlingMiddleware.js +17 -0
  8. package/lib/backend/config/middleware/errorHandlingMiddleware.test.js +44 -0
  9. package/lib/backend/config/middleware/loggingMiddleware.js +22 -0
  10. package/lib/backend/controllers/BundleGraphController.js +42 -0
  11. package/lib/backend/controllers/CacheDataController.js +143 -0
  12. package/lib/backend/controllers/FrontendAssetsController.js +61 -0
  13. package/lib/backend/controllers/TreeMapController.js +108 -0
  14. package/lib/backend/controllers/mcp/InspectorMCP.js +231 -0
  15. package/lib/backend/controllers/mcp/InspectorMCPController.js +16 -0
  16. package/lib/backend/errors/HTTPError.js +16 -0
  17. package/lib/backend/errors/HTTPError.test.js +21 -0
  18. package/lib/backend/index.js +177 -0
  19. package/lib/backend/services/AnalyticsService.js +86 -0
  20. package/lib/backend/services/LazyValue.js +33 -0
  21. package/lib/backend/services/LazyValue.test.js +12 -0
  22. package/lib/backend/services/buildJsonGraph.js +86 -0
  23. package/lib/backend/services/buildTreemap.js +140 -0
  24. package/lib/backend/services/buildTreemapBundle.test.js +298 -0
  25. package/lib/backend/services/findSourceCodeUrl.js +107 -0
  26. package/lib/backend/services/findSourceCodeUrl.test.js +216 -0
  27. package/lib/backend/services/getCacheStats.js +36 -0
  28. package/lib/backend/services/getCacheStats.test.js +162 -0
  29. package/lib/backend/services/getDisplayName.js +18 -0
  30. package/lib/backend/services/getDisplayName.test.js +71 -0
  31. package/lib/backend/services/loadCacheData.js +209 -0
  32. package/lib/backend/services/loadCacheData.test.js +79 -0
  33. package/lib/backend/testing/TemporaryDirectory.js +46 -0
  34. package/lib/frontend/node_modules/@atlaspack/packager-js/lib/dev-prelude.js +145 -0
  35. package/lib/frontend/node_modules/@atlaspack/packager-js/src/dev-prelude.js +145 -0
  36. package/package.json +75 -0
  37. package/screenshots/bottom-up.png +0 -0
  38. package/screenshots/cache-inspector.png +0 -0
  39. package/screenshots/treemap.png +0 -0
  40. package/src/backend/README.md +14 -0
  41. package/src/backend/cli.ts +3 -0
  42. package/src/backend/config/logger.ts +14 -0
  43. package/src/backend/config/middleware/cacheDataMiddleware.ts +94 -0
  44. package/src/backend/config/middleware/errorHandlingMiddleware.test.ts +52 -0
  45. package/src/backend/config/middleware/errorHandlingMiddleware.ts +20 -0
  46. package/src/backend/config/middleware/loggingMiddleware.ts +24 -0
  47. package/src/backend/controllers/BundleGraphController.ts +73 -0
  48. package/src/backend/controllers/CacheDataController.ts +187 -0
  49. package/src/backend/controllers/FrontendAssetsController.ts +28 -0
  50. package/src/backend/controllers/TreeMapController.ts +143 -0
  51. package/src/backend/controllers/mcp/InspectorMCP.ts +311 -0
  52. package/src/backend/controllers/mcp/InspectorMCPController.ts +17 -0
  53. package/src/backend/errors/HTTPError.test.ts +19 -0
  54. package/src/backend/errors/HTTPError.ts +14 -0
  55. package/src/backend/globals.d.ts +9 -0
  56. package/src/backend/index.ts +271 -0
  57. package/src/backend/services/AnalyticsService.ts +118 -0
  58. package/src/backend/services/LazyValue.test.ts +13 -0
  59. package/src/backend/services/LazyValue.ts +29 -0
  60. package/src/backend/services/buildJsonGraph.ts +124 -0
  61. package/src/backend/services/buildTreemap.ts +273 -0
  62. package/src/backend/services/buildTreemapBundle.test.ts +348 -0
  63. package/src/backend/services/findSourceCodeUrl.test.ts +228 -0
  64. package/src/backend/services/findSourceCodeUrl.ts +146 -0
  65. package/src/backend/services/getCacheStats.test.ts +169 -0
  66. package/src/backend/services/getCacheStats.ts +46 -0
  67. package/src/backend/services/getDisplayName.test.ts +84 -0
  68. package/src/backend/services/getDisplayName.ts +20 -0
  69. package/src/backend/services/loadCacheData.test.ts +101 -0
  70. package/src/backend/services/loadCacheData.ts +294 -0
  71. package/src/backend/testing/TemporaryDirectory.ts +50 -0
  72. package/src/frontend/.atlaspackrc +4 -0
  73. package/src/frontend/.eslintrc.json +19 -0
  74. package/src/frontend/dist/atlassian-dark-brand-refresh.91b786da.js +2 -0
  75. package/src/frontend/dist/atlassian-dark-brand-refresh.91b786da.js.map +1 -0
  76. package/src/frontend/dist/atlassian-dark-future.59ebadca.js +2 -0
  77. package/src/frontend/dist/atlassian-dark-future.59ebadca.js.map +1 -0
  78. package/src/frontend/dist/atlassian-dark-increased-contrast.ff6775f2.js +2 -0
  79. package/src/frontend/dist/atlassian-dark-increased-contrast.ff6775f2.js.map +1 -0
  80. package/src/frontend/dist/atlassian-dark.ad679134.js +2 -0
  81. package/src/frontend/dist/atlassian-dark.ad679134.js.map +1 -0
  82. package/src/frontend/dist/atlassian-legacy-dark.8aa27f7f.js +2 -0
  83. package/src/frontend/dist/atlassian-legacy-dark.8aa27f7f.js.map +1 -0
  84. package/src/frontend/dist/atlassian-legacy-light.2eb372ce.js +2 -0
  85. package/src/frontend/dist/atlassian-legacy-light.2eb372ce.js.map +1 -0
  86. package/src/frontend/dist/atlassian-light-brand-refresh.fadcab0a.js +2 -0
  87. package/src/frontend/dist/atlassian-light-brand-refresh.fadcab0a.js.map +1 -0
  88. package/src/frontend/dist/atlassian-light-future.612afe8a.js +2 -0
  89. package/src/frontend/dist/atlassian-light-future.612afe8a.js.map +1 -0
  90. package/src/frontend/dist/atlassian-light-increased-contrast.7161cd79.js +2 -0
  91. package/src/frontend/dist/atlassian-light-increased-contrast.7161cd79.js.map +1 -0
  92. package/src/frontend/dist/atlassian-light.bc343d4c.js +2 -0
  93. package/src/frontend/dist/atlassian-light.bc343d4c.js.map +1 -0
  94. package/src/frontend/dist/atlassian-shape.b92d69c0.js +2 -0
  95. package/src/frontend/dist/atlassian-shape.b92d69c0.js.map +1 -0
  96. package/src/frontend/dist/atlassian-spacing.60ddd8e7.js +2 -0
  97. package/src/frontend/dist/atlassian-spacing.60ddd8e7.js.map +1 -0
  98. package/src/frontend/dist/atlassian-typography-adg3.f88947f6.js +2 -0
  99. package/src/frontend/dist/atlassian-typography-adg3.f88947f6.js.map +1 -0
  100. package/src/frontend/dist/atlassian-typography-modernized.42016c51.js +2 -0
  101. package/src/frontend/dist/atlassian-typography-modernized.42016c51.js.map +1 -0
  102. package/src/frontend/dist/atlassian-typography-refreshed.ec0d111b.js +2 -0
  103. package/src/frontend/dist/atlassian-typography-refreshed.ec0d111b.js.map +1 -0
  104. package/src/frontend/dist/atlassian-typography.66d7e8f4.js +2 -0
  105. package/src/frontend/dist/atlassian-typography.66d7e8f4.js.map +1 -0
  106. package/src/frontend/dist/badge-light.7e55986a.png +0 -0
  107. package/src/frontend/dist/custom-theme.4680282a.js +2 -0
  108. package/src/frontend/dist/custom-theme.4680282a.js.map +1 -0
  109. package/src/frontend/dist/drag-handle.136830d3.js +2 -0
  110. package/src/frontend/dist/drag-handle.136830d3.js.map +1 -0
  111. package/src/frontend/dist/drag-handle.63bdb345.css +2 -0
  112. package/src/frontend/dist/drag-handle.63bdb345.css.map +1 -0
  113. package/src/frontend/dist/index.a41fafce.css +2 -0
  114. package/src/frontend/dist/index.a41fafce.css.map +1 -0
  115. package/src/frontend/dist/index.a4ce2b12.js +28 -0
  116. package/src/frontend/dist/index.a4ce2b12.js.map +1 -0
  117. package/src/frontend/dist/index.html +1 -0
  118. package/src/frontend/dist/index.runtime.a729d997.js +2 -0
  119. package/src/frontend/dist/index.runtime.a729d997.js.map +1 -0
  120. package/src/frontend/dist/refractor.2c1fd9a1.js +2 -0
  121. package/src/frontend/dist/refractor.2c1fd9a1.js.map +1 -0
  122. package/src/frontend/index.html +11 -0
  123. package/src/frontend/jest.config.js +16 -0
  124. package/src/frontend/package.json +64 -0
  125. package/src/frontend/src/APIError.test.ts +72 -0
  126. package/src/frontend/src/APIError.tsx +29 -0
  127. package/src/frontend/src/AppRoutes.tsx +56 -0
  128. package/src/frontend/src/hack-feature-flags.ts +6 -0
  129. package/src/frontend/src/main.tsx +50 -0
  130. package/src/frontend/src/test/stubCssModule.js +1 -0
  131. package/src/frontend/src/ui/App.module.css +122 -0
  132. package/src/frontend/src/ui/App.module.css.d.ts +8 -0
  133. package/src/frontend/src/ui/AppLayout/AppLayout.tsx +26 -0
  134. package/src/frontend/src/ui/AppLayout/SidebarNavigation/LinkItem.tsx +26 -0
  135. package/src/frontend/src/ui/AppLayout/SidebarNavigation/SidebarNavigation.tsx +45 -0
  136. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.module.css +12 -0
  137. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.module.css.d.ts +4 -0
  138. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.tsx +11 -0
  139. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.module.css +14 -0
  140. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.module.css.d.ts +3 -0
  141. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.tsx +45 -0
  142. package/src/frontend/src/ui/AppLayout/TopNavigation/badge-light.png +0 -0
  143. package/src/frontend/src/ui/AppLayout/TopNavigation/logo-light.png +0 -0
  144. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css +9 -0
  145. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css.d.ts +3 -0
  146. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.test.tsx +15 -0
  147. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.tsx +14 -0
  148. package/src/frontend/src/ui/app/StatsPage.tsx +77 -0
  149. package/src/frontend/src/ui/app/cache/CacheKeysIndexPage.tsx +13 -0
  150. package/src/frontend/src/ui/app/cache/CacheKeysPage.module.css +11 -0
  151. package/src/frontend/src/ui/app/cache/CacheKeysPage.module.css.d.ts +4 -0
  152. package/src/frontend/src/ui/app/cache/CacheKeysPage.tsx +23 -0
  153. package/src/frontend/src/ui/app/cache/[key]/CacheValuePage.tsx +40 -0
  154. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.module.css +40 -0
  155. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.module.css.d.ts +7 -0
  156. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.tsx +187 -0
  157. package/src/frontend/src/ui/app/cache-invalidation/CacheInvalidationPage.tsx +22 -0
  158. package/src/frontend/src/ui/app/cache-invalidation/[fileId]/CacheInvalidationFilePage.tsx +22 -0
  159. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.module.css +40 -0
  160. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.module.css.d.ts +7 -0
  161. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.tsx +185 -0
  162. package/src/frontend/src/ui/app/treemap/BottomPanelResizeState.test.ts +25 -0
  163. package/src/frontend/src/ui/app/treemap/BottomPanelResizeState.tsx +48 -0
  164. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.module.css +24 -0
  165. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.module.css.d.ts +6 -0
  166. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.tsx +47 -0
  167. package/src/frontend/src/ui/app/treemap/controllers/RelatedBundlesController.tsx +41 -0
  168. package/src/frontend/src/ui/app/treemap/controllers/UrlFocusController.tsx +33 -0
  169. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css +24 -0
  170. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css.d.ts +5 -0
  171. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.tsx +24 -0
  172. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css +13 -0
  173. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css.d.ts +5 -0
  174. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.tsx +53 -0
  175. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx +135 -0
  176. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css +7 -0
  177. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css.d.ts +3 -0
  178. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.tsx +123 -0
  179. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableModel.tsx +18 -0
  180. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css +20 -0
  181. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css.d.ts +6 -0
  182. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.tsx +79 -0
  183. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.test.ts +19 -0
  184. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.ts +24 -0
  185. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css +20 -0
  186. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css.d.ts +5 -0
  187. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.tsx +42 -0
  188. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css +29 -0
  189. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css.d.ts +6 -0
  190. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.tsx +107 -0
  191. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css +7 -0
  192. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css.d.ts +3 -0
  193. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.tsx +20 -0
  194. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/SourceCodeURL.tsx +5 -0
  195. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.module.css +13 -0
  196. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.module.css.d.ts +4 -0
  197. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.tsx +95 -0
  198. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css +6 -0
  199. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css.d.ts +3 -0
  200. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.tsx +49 -0
  201. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.module.css +5 -0
  202. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.module.css.d.ts +3 -0
  203. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.tsx +80 -0
  204. package/src/frontend/src/ui/app/treemap/ui/Treemap.tsx +14 -0
  205. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css +32 -0
  206. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css.d.ts +5 -0
  207. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.tsx +24 -0
  208. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css +14 -0
  209. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css.d.ts +4 -0
  210. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.tsx +271 -0
  211. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css +15 -0
  212. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css.d.ts +4 -0
  213. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.tsx +111 -0
  214. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.test.ts +27 -0
  215. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.ts +21 -0
  216. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/useMouseMoveController.ts +20 -0
  217. package/src/frontend/src/ui/globals.css +26 -0
  218. package/src/frontend/src/ui/globals.css.d.ts +1 -0
  219. package/src/frontend/src/ui/globals.d.ts +9 -0
  220. package/src/frontend/src/ui/model/ViewModel.test.ts +31 -0
  221. package/src/frontend/src/ui/model/ViewModel.ts +62 -0
  222. package/src/frontend/src/ui/not-found/NotFoundPage.module.css +7 -0
  223. package/src/frontend/src/ui/not-found/NotFoundPage.module.css.d.ts +3 -0
  224. package/src/frontend/src/ui/not-found/NotFoundPage.tsx +9 -0
  225. package/src/frontend/src/ui/types/Graph.tsx +12 -0
  226. package/src/frontend/src/ui/util/ErrorBoundary.module.css +3 -0
  227. package/src/frontend/src/ui/util/ErrorBoundary.module.css.d.ts +3 -0
  228. package/src/frontend/src/ui/util/ErrorBoundary.test.tsx +65 -0
  229. package/src/frontend/src/ui/util/ErrorBoundary.tsx +75 -0
  230. package/src/frontend/src/ui/util/colorPalette.tsx +122 -0
  231. package/src/frontend/src/ui/util/formatBytes.test.ts +13 -0
  232. package/src/frontend/src/ui/util/formatBytes.tsx +9 -0
  233. package/src/frontend/src/ui/util/getRandomDarkerColor.tsx +31 -0
  234. package/src/frontend/tsconfig.json +12 -0
  235. package/src/frontend/yarn.lock +0 -0
@@ -0,0 +1,187 @@
1
+ import {Router} from 'express';
2
+ import {getCacheStats} from '../services/getCacheStats';
3
+ import {
4
+ getCache,
5
+ getRequestTracker,
6
+ } from '../config/middleware/cacheDataMiddleware';
7
+
8
+ export function makeCacheDataController(): Router {
9
+ const router = Router();
10
+
11
+ router.get('/api/stats', (_req, res) => {
12
+ const cache = getCache(res);
13
+ const stats = getCacheStats(cache);
14
+ res.json(stats);
15
+ });
16
+
17
+ router.get('/api/cache-invalidation-files/', (req, res) => {
18
+ const limit = req.query.limit != null ? Number(req.query.limit) : 100;
19
+ const cursor = req.query.cursor as string | undefined;
20
+ const sortBy = req.query.sortBy as string;
21
+
22
+ const requestTracker = getRequestTracker(res);
23
+ // 0 is file
24
+ let files = requestTracker.graph.nodes
25
+ .filter((node: any) => node.type === 0)
26
+ .map((node: any) => ({
27
+ ...node,
28
+ invalidationCount: requestTracker.graph
29
+ .getNodeIdsConnectedTo(
30
+ requestTracker.graph.getNodeIdByContentKey(node.id),
31
+ -1,
32
+ )
33
+ // type 1 is request
34
+ .filter((node: any) => requestTracker.graph.getNode(node).type === 1)
35
+ .length,
36
+ }));
37
+
38
+ if (sortBy == null || sortBy === 'invalidationCount') {
39
+ files.sort((a: any, b: any) => b.invalidationCount - a.invalidationCount);
40
+ }
41
+
42
+ const count = files.length;
43
+ const cursorIndex =
44
+ cursor != null && cursor !== ''
45
+ ? files.findIndex((node: any) => node.id === cursor) + 1
46
+ : 0;
47
+ const resultFiles = files.slice(cursorIndex, cursorIndex + limit);
48
+
49
+ res.json({
50
+ files: resultFiles,
51
+ count,
52
+ nextPageCursor:
53
+ cursorIndex + limit < count
54
+ ? resultFiles[resultFiles.length - 1]
55
+ : null,
56
+ hasNextPage: cursorIndex + limit < count,
57
+ limit,
58
+ });
59
+ });
60
+
61
+ router.get('/api/cache-invalidation-files/:fileId', (req, res) => {
62
+ const requestTracker = getRequestTracker(res);
63
+ const file = requestTracker.graph.nodes.find(
64
+ (node: any) => node.id === req.params.fileId,
65
+ );
66
+ if (!file) {
67
+ res.status(400).json({error: 'File not found'});
68
+ return;
69
+ }
70
+
71
+ const nodeId = requestTracker.graph.getNodeIdByContentKey(file.id);
72
+ const nodes = requestTracker.graph.getNodeIdsConnectedTo(nodeId, -1);
73
+
74
+ const invalidatedNodes = nodes
75
+ .map((node: any) => requestTracker.graph.getNode(node))
76
+ .filter((node: any) => node.type === 1);
77
+
78
+ const response = {
79
+ file,
80
+ invalidatedNodesCount: invalidatedNodes.length,
81
+ };
82
+
83
+ res.json(response);
84
+ });
85
+
86
+ router.get(
87
+ '/api/cache-invalidation-files/:fileId/invalidated-nodes',
88
+ (req, res) => {
89
+ const limit = req.query.limit != null ? Number(req.query.limit) : 100;
90
+ const cursor = req.query.cursor as string | undefined;
91
+
92
+ const requestTracker = getRequestTracker(res);
93
+ const file = requestTracker.graph.nodes.find(
94
+ (node: any) => node.id === req.params.fileId,
95
+ );
96
+ if (!file) {
97
+ res.status(400).json({error: 'File not found'});
98
+ return;
99
+ }
100
+
101
+ const nodeId = requestTracker.graph.getNodeIdByContentKey(file.id);
102
+ const nodes = requestTracker.graph.getNodeIdsConnectedTo(nodeId, -1);
103
+
104
+ const invalidatedNodes = nodes
105
+ .map((node: any) => requestTracker.graph.getNode(node))
106
+ .filter((node: any) => node.type === 1);
107
+ const cursorIndex =
108
+ cursor != null && cursor !== ''
109
+ ? invalidatedNodes.findIndex((node: any) => node.id === cursor) + 1
110
+ : 0;
111
+ const resultNodes = invalidatedNodes.slice(
112
+ cursorIndex,
113
+ cursorIndex + limit,
114
+ );
115
+
116
+ const response = {
117
+ data: resultNodes,
118
+ count: invalidatedNodes.length,
119
+ nextPageCursor:
120
+ cursorIndex + limit < invalidatedNodes.length
121
+ ? resultNodes[resultNodes.length - 1]
122
+ : null,
123
+ hasNextPage: cursorIndex + limit < invalidatedNodes.length,
124
+ limit,
125
+ };
126
+
127
+ res.json(response);
128
+ },
129
+ );
130
+
131
+ router.get('/api/cache-keys/', (req, res) => {
132
+ const cache = getCache(res);
133
+ const sortBy = req.query.sortBy as string;
134
+ const limit = req.query.limit != null ? Number(req.query.limit) : 100;
135
+ const cursor = req.query.cursor as string | undefined;
136
+
137
+ let resultKeys = Array.from(cache.keys());
138
+ if (sortBy === 'size') {
139
+ const sizes = resultKeys.map((key: any) => [
140
+ key,
141
+ cache.getBlobSync(key).length,
142
+ ]);
143
+ sizes.sort((a, b) => (b[1] as number) - (a[1] as number));
144
+ resultKeys = sizes.map(([key]: any) => key as string);
145
+ }
146
+
147
+ const count = resultKeys.length;
148
+ const cursorIndex =
149
+ cursor != null ? resultKeys.indexOf(cursor as string) + 1 : 0;
150
+ resultKeys = resultKeys.slice(cursorIndex, cursorIndex + limit);
151
+
152
+ res.json({
153
+ keys: resultKeys,
154
+ count,
155
+ nextPageCursor:
156
+ cursorIndex + limit < count ? resultKeys[resultKeys.length - 1] : null,
157
+ hasNextPage: cursorIndex + limit < count,
158
+ limit,
159
+ });
160
+ });
161
+
162
+ router.get('/api/cache-value/:key', (req, res) => {
163
+ const cache = getCache(res);
164
+ const key = req.params.key;
165
+
166
+ try {
167
+ const value = cache.getBlobSync(key);
168
+ // bigger than 1MB
169
+ if (typeof value.length === 'number' && value.length > 1024 * 1024) {
170
+ res.json({
171
+ size: value.length,
172
+ value: value.slice(0, 1024 * 1024).toString('utf-8'),
173
+ });
174
+ return;
175
+ }
176
+
177
+ res.json({
178
+ size: value.length,
179
+ value: value.toString('utf-8'),
180
+ });
181
+ } catch (error) {
182
+ res.status(500).json({error: (error as Error).message});
183
+ }
184
+ });
185
+
186
+ return router;
187
+ }
@@ -0,0 +1,28 @@
1
+ import path from 'path';
2
+ import express, {Router} from 'express';
3
+
4
+ /**
5
+ * On production builds, we intend to serve a pre-built version of the front-end `dist`
6
+ * directory in `src/frontend/dist`.
7
+ *
8
+ * On development mode, you should be running the front-end dev-server separately
9
+ * and pointing it at this host.
10
+ */
11
+ export function makeFrontendAssetsController() {
12
+ const router = Router();
13
+
14
+ router.use(
15
+ express.static(path.join(__dirname, '../../../src/frontend/dist')),
16
+ );
17
+ router.use('/app/', (req, res, next) => {
18
+ if (req.method === 'GET') {
19
+ res.sendFile(
20
+ path.join(__dirname, '../../../src/frontend/dist/index.html'),
21
+ );
22
+ } else {
23
+ next();
24
+ }
25
+ });
26
+
27
+ return router;
28
+ }
@@ -0,0 +1,143 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ /* eslint-disable monorepo/no-internal-import */
3
+ import {Router} from 'express';
4
+ import path from 'path';
5
+ import {
6
+ getBundleGraph,
7
+ getTreemap,
8
+ } from '../config/middleware/cacheDataMiddleware';
9
+ // @ts-expect-error TS2749
10
+ import {Node} from '@atlaspack/core/lib/types.js';
11
+ import {ALL_EDGE_TYPES} from '@atlaspack/graph';
12
+ import {SourceCodeURL} from '../services/findSourceCodeUrl';
13
+ import {logger} from '../config/logger';
14
+
15
+ export interface MakeTreemapControllerParams {
16
+ sourceCodeURL: SourceCodeURL | null;
17
+ projectRoot: string;
18
+ repositoryRoot: string;
19
+ }
20
+
21
+ export function makeTreemapController({
22
+ sourceCodeURL,
23
+ projectRoot,
24
+ repositoryRoot,
25
+ }: MakeTreemapControllerParams): Router {
26
+ const router = Router();
27
+
28
+ router.get('/api/treemap/reasons', (req, res) => {
29
+ const bundleGraph = getBundleGraph(res);
30
+
31
+ const queryPath = req.query.path as string;
32
+ const bundle = req.query.bundle as string;
33
+
34
+ const bundleNode = bundleGraph._graph.getNode(
35
+ bundleGraph._graph.getNodeIdByContentKey(bundle),
36
+ );
37
+
38
+ const relevantPaths: string[][] = [];
39
+ let tooManyPaths = false;
40
+ bundleGraph.traverseBundle(bundleNode.value, {
41
+ enter(
42
+ node: Node,
43
+ context: string[],
44
+ actions: {skipChildren: () => void; stop: () => void},
45
+ ) {
46
+ if (context == null) {
47
+ context = [];
48
+ }
49
+
50
+ if (node.type === 'asset') {
51
+ context.push(path.relative(repositoryRoot, node.value.filePath));
52
+ }
53
+
54
+ if (node.type === 'dependency') {
55
+ try {
56
+ const childNodeIds = bundleGraph._graph.getNodeIdsConnectedFrom(
57
+ bundleGraph._graph.getNodeIdByContentKey(node.id),
58
+ ALL_EDGE_TYPES,
59
+ );
60
+ let isParent = false;
61
+ for (const childNodeId of childNodeIds) {
62
+ const childNode = bundleGraph._graph.getNode(childNodeId);
63
+ if (
64
+ childNode.type === 'asset' &&
65
+ path
66
+ .relative(repositoryRoot, childNode.value.filePath)
67
+ .startsWith(queryPath)
68
+ ) {
69
+ actions.skipChildren();
70
+ isParent = true;
71
+ }
72
+ }
73
+
74
+ // For some reason we visit all nodes from the bundle, so we need to filter out
75
+ // stuff that is directly connected to the bundle node, since that's not useful
76
+ // information.
77
+ // e.g.: On the cases where the file is included directly to the bundle either due
78
+ // to manual bundling or entry dependencies, the user probably already knows about
79
+ // it.
80
+ if (isParent && context.length > 1) {
81
+ relevantPaths.push(context.slice());
82
+
83
+ if (relevantPaths.length > 50) {
84
+ tooManyPaths = true;
85
+ actions.stop();
86
+ }
87
+ }
88
+ } catch (err) {
89
+ logger.error(err);
90
+ }
91
+ }
92
+
93
+ return context;
94
+ },
95
+ exit(node: Node, context: string[] = []) {
96
+ if (node.type === 'asset') {
97
+ context.pop();
98
+ }
99
+ return context;
100
+ },
101
+ });
102
+
103
+ res.json({
104
+ tooManyPaths,
105
+ relevantPaths,
106
+ sourceCodeURL,
107
+ projectRoot,
108
+ repositoryRoot,
109
+ });
110
+ });
111
+
112
+ router.get('/api/treemap', (req, res) => {
113
+ const treemap = getTreemap(res);
114
+
115
+ const limit = Number(req.query.limit ?? 10000);
116
+ const offset = Number(req.query.offset ?? 0);
117
+
118
+ const bundleId = req.query.bundle as string | null;
119
+ let bundles = treemap!.bundles;
120
+ if (bundleId) {
121
+ bundles = bundles.filter((bundle) => bundle.id === bundleId);
122
+ } else {
123
+ bundles = bundles.map((bundle) => ({
124
+ ...bundle,
125
+ assetTree: {
126
+ id: `${bundle.id}::/`,
127
+ path: '',
128
+ children: {},
129
+ size: bundle.size,
130
+ },
131
+ }));
132
+ }
133
+
134
+ res.json({
135
+ bundles: bundles.slice(offset, offset + limit),
136
+ next: offset + limit < bundles.length ? offset + limit : null,
137
+ count: bundles.length,
138
+ totalSize: treemap!.totalSize,
139
+ });
140
+ });
141
+
142
+ return router;
143
+ }
@@ -0,0 +1,311 @@
1
+ /* eslint-disable monorepo/no-internal-import */
2
+ import {
3
+ McpServer,
4
+ ResourceTemplate,
5
+ } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import {
7
+ isInitializeRequest,
8
+ ListResourcesResult,
9
+ ReadResourceResult,
10
+ Resource,
11
+ } from '@modelcontextprotocol/sdk/types.js';
12
+ import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js';
13
+ import {randomUUID} from 'crypto';
14
+ import {Request, Response} from 'express';
15
+ import {z} from 'zod';
16
+ import {LMDBLiteCache} from '@atlaspack/cache';
17
+ import {
18
+ getBundleGraph,
19
+ getCache,
20
+ getTreemap,
21
+ } from '../../config/middleware/cacheDataMiddleware';
22
+ // @ts-expect-error TS2749
23
+ import BundleGraph from '@atlaspack/core/lib/BundleGraph.js';
24
+ import {Treemap} from '../../services/buildTreemap';
25
+
26
+ /**
27
+ * Incomplete, toy MCP server for the Atlaspack Inspector.
28
+ */
29
+ function makeInspectorMCPServer({
30
+ cache,
31
+ bundleGraph,
32
+ treemap,
33
+ }: {
34
+ cache: LMDBLiteCache;
35
+ bundleGraph: BundleGraph;
36
+ treemap: Treemap;
37
+ }): McpServer {
38
+ const server = new McpServer({
39
+ name: 'atlaspack-inspector-mcp',
40
+ version: '1.0.0',
41
+ });
42
+
43
+ server.registerTool(
44
+ 'list-treemap-bundles',
45
+ {
46
+ title: 'List Treemap Bundles',
47
+ description: `List the bundles in the treemap.`,
48
+ inputSchema: {},
49
+ },
50
+ () => {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: [
56
+ `Treemap bundles:`,
57
+ ...treemap.bundles.flatMap((bundle) => [
58
+ `- ${bundle.displayName} - ${bundle.id} - atlaspack://bundle-info/${bundle.id}`,
59
+ ` * ${bundle.size} bytes`,
60
+ ` * ${bundle.filePath} file path`,
61
+ ` * Run 'query-treemap ${bundle.id}' to get more information about this bundle`,
62
+ ]),
63
+ ].join('\n'),
64
+ },
65
+ ],
66
+ };
67
+ },
68
+ );
69
+
70
+ server.registerTool(
71
+ 'query-treemap',
72
+ {
73
+ title: 'Query Treemap',
74
+ description: `Query the treemap for information about a given bundle.`,
75
+ inputSchema: {
76
+ bundleId: z.string(),
77
+ },
78
+ },
79
+ ({bundleId}) => {
80
+ const bundle = treemap.bundles.find((bundle) => bundle.id === bundleId);
81
+
82
+ if (!bundle) {
83
+ return {
84
+ content: [{type: 'text', text: `Bundle not found: ${bundleId}`}],
85
+ };
86
+ }
87
+
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text',
92
+ text: [
93
+ `Bundle: ${bundle.displayName}`,
94
+ `Bundle size: ${bundle.size} bytes`,
95
+ ].join('\n'),
96
+ },
97
+ {
98
+ type: 'text',
99
+ text: `Raw treemap JSON:\n${JSON.stringify(bundle)}`,
100
+ },
101
+ ],
102
+ };
103
+ },
104
+ );
105
+
106
+ server.registerTool(
107
+ 'get-resolved-imports',
108
+ {
109
+ title: 'Get Resolved Imports',
110
+ description: `Get the resolved imports for a given JavaScript/TypeScript file, if it appears on the output bundles.`,
111
+ inputSchema: {
112
+ file: z.string(),
113
+ },
114
+ },
115
+ ({file}) => {
116
+ const assetId = bundleGraph._graph.nodes.find((node: any) => {
117
+ return node.type === 'asset' && node.value.filePath === file;
118
+ })?.id;
119
+
120
+ if (!assetId) {
121
+ return {
122
+ content: [
123
+ {type: 'text', text: `File not found in any bundles: ${file}`},
124
+ ],
125
+ };
126
+ }
127
+
128
+ const asset = bundleGraph.getAssetById(assetId);
129
+ const bundlesWithAsset = bundleGraph.getBundlesWithAsset(asset);
130
+
131
+ return {
132
+ content: [
133
+ {
134
+ type: 'text',
135
+ text: [
136
+ `Bundles with asset:`,
137
+ ...bundlesWithAsset.map(
138
+ (bundle: any) =>
139
+ `- ${bundle.id} - ${bundle.displayName} - atlaspack://bundle-info/${bundle.id}`,
140
+ ),
141
+ ].join('\n'),
142
+ },
143
+ {
144
+ type: 'text',
145
+ text: `Raw asset JSON: ${JSON.stringify(asset)}`,
146
+ },
147
+ ],
148
+ };
149
+ },
150
+ );
151
+
152
+ server.registerResource(
153
+ 'treemap-bundle-info',
154
+ new ResourceTemplate('atlaspack://bundle-info/{id}', {
155
+ list(): ListResourcesResult {
156
+ const resources: Resource[] = treemap.bundles
157
+ .slice(0, 10)
158
+ .map((bundle) => ({
159
+ uri: `atlaspack://bundle-info/${bundle.id}`,
160
+ name: `Bundle: ${bundle.displayName}`,
161
+ }));
162
+
163
+ return {
164
+ resources,
165
+ };
166
+ },
167
+ }),
168
+ {
169
+ name: 'Atlaspack Treemap Bundle Information',
170
+ description: `Information about a built JavaScript bundle in the application and its bundle size. Can be used to list available bundles in the application.`,
171
+ },
172
+ (uri: URL): ReadResourceResult => {
173
+ const id = uri.pathname.split('/').slice(1).join('/');
174
+ const bundle = treemap.bundles.find((bundle) => bundle.id === id);
175
+
176
+ if (!bundle) {
177
+ return {
178
+ contents: [],
179
+ };
180
+ }
181
+
182
+ return {
183
+ contents: [
184
+ {
185
+ uri: uri.toString(),
186
+ text: [
187
+ `Bundle: ${bundle.displayName}`,
188
+ `Bundle size: ${bundle.size} bytes`,
189
+ ].join('\n---\n'),
190
+ },
191
+ ],
192
+ };
193
+ },
194
+ );
195
+
196
+ server.registerResource(
197
+ 'cache-key',
198
+ new ResourceTemplate('atlaspack://cache/{key}', {
199
+ list: undefined,
200
+ }),
201
+ {
202
+ name: 'Atlaspack Cache Entry',
203
+ },
204
+ (uri: URL): ReadResourceResult => {
205
+ const key = uri.pathname.split('/').slice(1).join('/');
206
+ const value = cache.getBlobSync(key);
207
+
208
+ return {
209
+ contents: [
210
+ {
211
+ uri: uri.toString(),
212
+ type: 'text',
213
+ text: [
214
+ `Cache key: ${key}`,
215
+ `Cache value total size: ${value.length} bytes`,
216
+ `First 500KB of cache value:\n\n${value.subarray(0, 1024 * 500).toString()}`,
217
+ ].join('\n---\n'),
218
+ },
219
+ ],
220
+ };
221
+ },
222
+ );
223
+
224
+ return server;
225
+ }
226
+
227
+ export interface InspectorMCPSession {
228
+ transport: StreamableHTTPServerTransport;
229
+ server: McpServer;
230
+ }
231
+
232
+ export class InspectorMCP {
233
+ private sessions: Map<string, InspectorMCPSession> = new Map();
234
+
235
+ constructor() {}
236
+
237
+ getSession(request: Request): InspectorMCPSession | undefined {
238
+ const sessionId = request.headers['mcp-session-id'] as string | undefined;
239
+ if (!sessionId) {
240
+ return undefined;
241
+ }
242
+ return this.sessions.get(sessionId);
243
+ }
244
+
245
+ async get(request: Request, response: Response): Promise<void> {
246
+ const session = this.getSession(request);
247
+ if (!session) {
248
+ response.status(400).send('Invalid or missing session ID');
249
+ return;
250
+ }
251
+
252
+ await session.transport.handleRequest(request, response);
253
+ }
254
+
255
+ async post(request: Request, response: Response): Promise<void> {
256
+ const sessionId = request.headers['mcp-session-id'] as string | undefined;
257
+ let transport: StreamableHTTPServerTransport;
258
+
259
+ if (sessionId && this.sessions.has(sessionId)) {
260
+ transport = this.sessions.get(sessionId)!.transport;
261
+ } else if (!sessionId && isInitializeRequest(request.body)) {
262
+ transport = await this.createSession(response);
263
+ } else {
264
+ response.status(400).json({
265
+ jsonrpc: '2.0',
266
+ error: {
267
+ code: -32000,
268
+ message: 'Bad Request: No valid session ID provided',
269
+ },
270
+ id: null,
271
+ });
272
+ return;
273
+ }
274
+
275
+ await transport.handleRequest(request, response, request.body);
276
+ }
277
+
278
+ private async createSession(
279
+ response: Response,
280
+ ): Promise<StreamableHTTPServerTransport> {
281
+ const cache = getCache(response);
282
+ const bundleGraph = getBundleGraph(response);
283
+ const treemap = getTreemap(response);
284
+
285
+ const server = makeInspectorMCPServer({
286
+ cache,
287
+ bundleGraph,
288
+ treemap,
289
+ });
290
+
291
+ const transport = new StreamableHTTPServerTransport({
292
+ sessionIdGenerator: () => randomUUID(),
293
+ onsessioninitialized: (sessionId) => {
294
+ this.sessions.set(sessionId, {
295
+ transport,
296
+ server,
297
+ });
298
+ },
299
+ });
300
+
301
+ transport.onclose = () => {
302
+ if (transport.sessionId) {
303
+ this.sessions.delete(transport.sessionId);
304
+ }
305
+ };
306
+
307
+ await server.connect(transport);
308
+
309
+ return transport;
310
+ }
311
+ }
@@ -0,0 +1,17 @@
1
+ import {Router} from 'express';
2
+ import {InspectorMCP} from './InspectorMCP';
3
+
4
+ export function makeInspectorMCPController(): Router {
5
+ const router = Router();
6
+ const mcp = new InspectorMCP();
7
+
8
+ router.post('/api/mcp', (req, res) => {
9
+ mcp.post(req, res);
10
+ });
11
+
12
+ router.get('/api/mcp', (req, res) => {
13
+ mcp.get(req, res);
14
+ });
15
+
16
+ return router;
17
+ }
@@ -0,0 +1,19 @@
1
+ import assert from 'assert';
2
+ import {HTTPError} from './HTTPError';
3
+
4
+ describe('HTTPError', function () {
5
+ it('should create HTTPError with message and status', function () {
6
+ const error = new HTTPError('Not found', 404);
7
+
8
+ assert.equal(error.message, 'Not found');
9
+ assert.equal(error.status, 404);
10
+ assert(error instanceof Error);
11
+ assert(error instanceof HTTPError);
12
+ });
13
+
14
+ it('should be throwable', function () {
15
+ assert.throws(() => {
16
+ throw new HTTPError('Server error', 500);
17
+ }, HTTPError);
18
+ });
19
+ });