@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,14 @@
1
+ /**
2
+ * A custom error class for HTTP errors.
3
+ *
4
+ * The {@link errorHandlingMiddleware} will automatically set HTTP response
5
+ * status codes for instances of this class.
6
+ */
7
+ export class HTTPError extends Error {
8
+ constructor(
9
+ message: string,
10
+ public status: number,
11
+ ) {
12
+ super(message);
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ declare module '@atlaspack/query' {
2
+ import type {BundleGraph, RequestTracker, AssetGraph} from '@atlaspack/core';
3
+
4
+ export function loadGraphs(cacheDir: string): Promise<{
5
+ requestTracker: RequestTracker;
6
+ bundleGraph: BundleGraph;
7
+ assetGraph: AssetGraph;
8
+ }>;
9
+ }
@@ -0,0 +1,271 @@
1
+ /* eslint-disable import/first */
2
+ // Stateful import to ensure serializers are loaded
3
+ require('@atlaspack/core');
4
+
5
+ import {program} from 'commander';
6
+ import express from 'express';
7
+ import cors from 'cors';
8
+ import path from 'path';
9
+ import {setFeatureFlags, DEFAULT_FEATURE_FLAGS} from '@atlaspack/feature-flags';
10
+
11
+ import {CacheData, loadCacheData} from './services/loadCacheData';
12
+ import {logger} from './config/logger';
13
+ import {errorHandlingMiddleware} from './config/middleware/errorHandlingMiddleware';
14
+ import {loggingMiddleware} from './config/middleware/loggingMiddleware';
15
+ import {makeFrontendAssetsController} from './controllers/FrontendAssetsController';
16
+ import {cacheDataMiddleware} from './config/middleware/cacheDataMiddleware';
17
+ import {makeBundleGraphController} from './controllers/BundleGraphController';
18
+ import {makeTreemapController} from './controllers/TreeMapController';
19
+ import {makeCacheDataController} from './controllers/CacheDataController';
20
+ import {
21
+ findProjectRoot,
22
+ findRepositoryRoot,
23
+ findSourceCodeURL,
24
+ SourceCodeURL,
25
+ } from './services/findSourceCodeUrl';
26
+ import {AddressInfo} from 'net';
27
+ import {makeInspectorMCPController} from './controllers/mcp/InspectorMCPController';
28
+ import {spawn} from 'child_process';
29
+ import {analyticsService} from './services/AnalyticsService';
30
+
31
+ /**
32
+ * We split preparing cache data and building the app.
33
+ *
34
+ * The cache is opened once and some models are created from it.
35
+ *
36
+ * These models are shared through the application on the `res.locals` express field.
37
+ */
38
+ interface ConfigureInspectorAppParams {
39
+ /**
40
+ * A path to the cache directory or a path to a project.
41
+ *
42
+ * If a cache isn't found in this path, the tool will traverse up until it finds
43
+ * a suitable root.
44
+ *
45
+ * Once a cache is found, a `.git` directory will be looked-up to find a "project root".
46
+ *
47
+ * This will be used to find files and to find source code URLs on GitHub or BitBucket
48
+ * cloud.
49
+ */
50
+ target: string;
51
+ /**
52
+ * A path to the project root.
53
+ *
54
+ * If not provided, the tool will traverse up until it finds a suitable root.
55
+ */
56
+ projectRoot?: string;
57
+ }
58
+
59
+ /**
60
+ * Configures the inspector app.
61
+ *
62
+ * - Find paths for the source repository, project and cache.
63
+ * - Open the cache and deserialize bundler data out of it.
64
+ * - Build the tree-map model.
65
+ */
66
+ export function configureInspectorApp({
67
+ projectRoot: projectRootFromFlags,
68
+ target,
69
+ }: ConfigureInspectorAppParams): BuildInspectorAppParams {
70
+ const flags = {
71
+ ...DEFAULT_FEATURE_FLAGS,
72
+ cachePerformanceImprovements: true,
73
+ };
74
+ setFeatureFlags(flags);
75
+
76
+ const projectRoot =
77
+ projectRootFromFlags ?? findProjectRoot(target) ?? path.dirname(target);
78
+ const repositoryRoot = findRepositoryRoot(target) ?? path.dirname(target);
79
+
80
+ const sourceCodeURL = findSourceCodeURL(target);
81
+
82
+ logger.debug(
83
+ {target, projectRoot, repositoryRoot, sourceCodeURL},
84
+ 'Found paths',
85
+ );
86
+
87
+ const cacheData = loadCacheData({target, projectRoot, repositoryRoot});
88
+
89
+ return {
90
+ cacheData,
91
+ projectRoot,
92
+ repositoryRoot,
93
+ sourceCodeURL,
94
+ };
95
+ }
96
+
97
+ export interface BuildInspectorAppParams {
98
+ /**
99
+ * A promise to the cache data.
100
+ */
101
+ cacheData: Promise<CacheData>;
102
+ /**
103
+ * The atlaspack project root.
104
+ */
105
+ projectRoot: string;
106
+ /**
107
+ * The repository root path for the target project. This will be used to link
108
+ * to the source code on GitHub or BitBucket.
109
+ */
110
+ repositoryRoot: string;
111
+ /**
112
+ * The source code URL for the target project. The parsed remote URL.
113
+ */
114
+ sourceCodeURL: SourceCodeURL | null;
115
+ }
116
+
117
+ /**
118
+ * Wire-up the express server app.
119
+ */
120
+ export function buildInspectorApp({
121
+ cacheData,
122
+ projectRoot,
123
+ repositoryRoot,
124
+ sourceCodeURL,
125
+ }: BuildInspectorAppParams): express.Express {
126
+ const app = express();
127
+
128
+ app.use(loggingMiddleware());
129
+ app.use(
130
+ cors({
131
+ origin: /http:\/\/localhost:(\d+)/,
132
+ credentials: true,
133
+ }),
134
+ );
135
+ app.use(express.json());
136
+
137
+ app.use(cacheDataMiddleware(cacheData));
138
+ app.use(makeFrontendAssetsController());
139
+ app.use(makeBundleGraphController({projectRoot, repositoryRoot}));
140
+ app.use(
141
+ makeTreemapController({
142
+ projectRoot,
143
+ repositoryRoot,
144
+ sourceCodeURL,
145
+ }),
146
+ );
147
+ app.use(makeInspectorMCPController());
148
+ app.use(makeCacheDataController());
149
+ app.use(errorHandlingMiddleware);
150
+
151
+ return app;
152
+ }
153
+
154
+ /**
155
+ * Executes `atlaspack build` to build the client application for the inspector.
156
+ *
157
+ * The inspector requires `cachePerformanceImprovements` to be enabled.
158
+ *
159
+ * @param targets - The targets/entry-points to build.
160
+ */
161
+ async function buildClientApplicationForInspector(targets: string[]) {
162
+ logger.info({targets}, 'Building app...');
163
+
164
+ const child = spawn(
165
+ 'yarn',
166
+ [
167
+ 'atlaspack',
168
+ 'build',
169
+ '--feature-flag',
170
+ 'cachePerformanceImprovements=true',
171
+ ...targets,
172
+ ],
173
+ {
174
+ shell: true,
175
+ stdio: 'inherit',
176
+ },
177
+ );
178
+
179
+ await new Promise((resolve, reject) => {
180
+ child.on('error', (error) => {
181
+ logger.error(error, 'Build error');
182
+ process.exitCode = 1;
183
+
184
+ reject(error);
185
+ });
186
+
187
+ child.on('exit', (code) => {
188
+ logger.info(`Build process exited with code ${code}`);
189
+ if (code !== 0) {
190
+ process.exitCode = code ?? 1;
191
+ reject(new Error(`Build failed with code ${code}`));
192
+ } else {
193
+ resolve(null);
194
+ }
195
+ });
196
+ });
197
+ }
198
+
199
+ /**
200
+ * CLI entry point for `@atlaspack/inspector`.
201
+ *
202
+ * Usage:
203
+ *
204
+ * ```bash
205
+ * yarn @atlaspack/inspector --target ./path/to/cache --project-root ./path/to/project
206
+ * ```
207
+ */
208
+ export function main() {
209
+ const version = require('../../package.json').version;
210
+ let isStartCommand = false;
211
+ program.name('atlaspack-inspector').version(version);
212
+
213
+ analyticsService.sendEvent({
214
+ data: {
215
+ name: 'atlaspack-inspector-start',
216
+ action: 'atlaspack-inspector-start',
217
+ },
218
+ });
219
+
220
+ const command = program
221
+ .command('start [options]', {isDefault: true})
222
+ .description('Start the inspector server')
223
+ .option('-t, --target <path>', 'Path to the target cache', process.cwd())
224
+ .option('-p, --port <port>', 'Port to run the server on', '3000')
225
+ .option('--project-root <path>', 'Path to the project root', undefined)
226
+ .action(() => {
227
+ isStartCommand = true;
228
+ });
229
+
230
+ program
231
+ .command('build <target...>')
232
+ .description('Build an app with atlaspack-inspector required feature-flags')
233
+ .action(async (targets) => {
234
+ await buildClientApplicationForInspector(targets);
235
+ });
236
+
237
+ program.parse(process.argv);
238
+
239
+ if (!isStartCommand) {
240
+ return;
241
+ }
242
+
243
+ const options = command.opts();
244
+
245
+ const inspectorAppParams = configureInspectorApp({
246
+ target: options.target,
247
+ projectRoot: options.projectRoot,
248
+ });
249
+ const app = buildInspectorApp(inspectorAppParams);
250
+
251
+ const port = Number(options.port ?? process.env.PORT ?? 3000);
252
+
253
+ const server = app.listen(port, () => {
254
+ const address: AddressInfo | string | null = server.address();
255
+ if (address == null) {
256
+ logger.error('Server did not start correctly');
257
+ } else {
258
+ const addressString =
259
+ typeof address === 'string'
260
+ ? address
261
+ : `http://localhost:${address.port}`;
262
+
263
+ logger.info(`Server is running on ${addressString}`);
264
+ }
265
+ });
266
+ server.on('error', (error) => {
267
+ logger.error(error, 'HTTP Server error');
268
+ });
269
+
270
+ return server;
271
+ }
@@ -0,0 +1,118 @@
1
+ import {randomUUID} from 'crypto';
2
+ import {logger} from '../config/logger';
3
+
4
+ export type AnalyticsServiceAvailableResponse =
5
+ | {
6
+ available: true;
7
+ name: string;
8
+ version: string;
9
+ }
10
+ | {
11
+ available: false;
12
+ };
13
+
14
+ export interface AnalyticsEvent {
15
+ id?: string;
16
+ cwd?: string;
17
+ data: {
18
+ name?: string;
19
+ category?: string;
20
+ status?: 'success' | 'failure';
21
+ action: string;
22
+ startTimestamp?: Date;
23
+ product?: string;
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Reports analytics events to a daemon server running at port 16621.
29
+ *
30
+ * The application will automatically check if the service is available and will not report
31
+ * anything if it is not.
32
+ */
33
+ export class AnalyticsService {
34
+ private readonly daemonUrl: string = 'http://localhost:16621';
35
+ private isAvailable: boolean | null = null;
36
+
37
+ /**
38
+ * Return the analytics service status.
39
+ */
40
+ public async checkAvailable(): Promise<AnalyticsServiceAvailableResponse> {
41
+ const response = await fetch(`${this.daemonUrl}/version`);
42
+ if (!response.ok) {
43
+ const status = response.status;
44
+ const body = await response.json();
45
+ logger.warn(
46
+ {body, status},
47
+ 'Failed to check analytics service availability',
48
+ );
49
+ this.isAvailable = false;
50
+ return {available: false};
51
+ }
52
+
53
+ const data: any = await response.json();
54
+
55
+ this.isAvailable = true;
56
+
57
+ return {
58
+ available: data.version !== 'unknown',
59
+ name: data.name,
60
+ version: data.version,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Record an analytics event.
66
+ */
67
+ async sendEvent(event: AnalyticsEvent): Promise<void> {
68
+ try {
69
+ if (this.isAvailable == null) {
70
+ const available = await this.checkAvailable();
71
+ logger.debug(
72
+ available,
73
+ `Analytics service is ${available.available ? 'available' : 'not available'}`,
74
+ );
75
+ }
76
+ if (!this.isAvailable) {
77
+ return;
78
+ }
79
+
80
+ const body = {
81
+ id: `atlaspack-inspector-${randomUUID()}`,
82
+ cwd: process.cwd(),
83
+ type: 'DISCRETE_EVENT',
84
+ ...event,
85
+ data: {
86
+ startTimestamp: new Date().toISOString(),
87
+ status: 'success',
88
+ product: 'atlaspack-inspector',
89
+ category: 'atlaspack-internal',
90
+ name: 'atlaspack-inspector',
91
+ ...event.data,
92
+ },
93
+ };
94
+ logger.debug({event: body}, 'Tracking analytics event');
95
+ const response = await fetch(`${this.daemonUrl}/events`, {
96
+ method: 'POST',
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ body: JSON.stringify(body),
101
+ });
102
+
103
+ if (!response.ok) {
104
+ const status = response.status;
105
+ const body = await response.json();
106
+ logger.warn({body, status}, 'Failed to send analytics event, skipping');
107
+ return;
108
+ }
109
+
110
+ const json = await response.json();
111
+ logger.debug({json}, 'Analytics event sent');
112
+ } catch (err) {
113
+ logger.warn({err}, 'Failed to send analytics event');
114
+ }
115
+ }
116
+ }
117
+
118
+ export const analyticsService = new AnalyticsService();
@@ -0,0 +1,13 @@
1
+ import {LazyValue} from './LazyValue';
2
+
3
+ describe('LazyValue', () => {
4
+ it('should return the value by calling the factory function once', () => {
5
+ const mock = jest.fn().mockImplementation(() => 'test');
6
+ const lazyValue = new LazyValue(mock);
7
+
8
+ expect(lazyValue.get()).toBe('test');
9
+ expect(lazyValue.get()).toBe('test');
10
+
11
+ expect(mock).toHaveBeenCalledTimes(1);
12
+ });
13
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A value that is computed once on read, then cached.
3
+ *
4
+ * @example
5
+ *
6
+ * ```ts
7
+ * const lazyComputation = new LazyValue(() => doExpensiveComputation());
8
+ *
9
+ * const result1 = lazyComputation.get();
10
+ * const result2 = lazyComputation.get();
11
+ *
12
+ * assert(result1 === result2);
13
+ * ```
14
+ */
15
+ export class LazyValue<T> {
16
+ private value: T | null = null;
17
+
18
+ constructor(private readonly factory: () => T) {}
19
+
20
+ /**
21
+ * Builds the value if it hasn't been built and returns it.
22
+ */
23
+ get(): T {
24
+ if (this.value == null) {
25
+ this.value = this.factory();
26
+ }
27
+ return this.value;
28
+ }
29
+ }
@@ -0,0 +1,124 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ /* eslint-disable monorepo/no-internal-import */
3
+ // @ts-expect-error TS2749
4
+ import type {Node} from '@atlaspack/core/lib/types.js';
5
+ import {ALL_EDGE_TYPES, ContentGraph} from '@atlaspack/graph';
6
+ import {getDisplayName} from './getDisplayName';
7
+
8
+ export interface JsonGraph<T> {
9
+ nodes: Array<{
10
+ id: string;
11
+ nodeId: string;
12
+ path: string[] | null;
13
+ displayName: string;
14
+ level: number;
15
+ edges: string[];
16
+ extra: T | null;
17
+ }>;
18
+ }
19
+
20
+ /**
21
+ * Build a simplified representation of a graph for a front-end to render.
22
+ *
23
+ * The graph is simplified by:
24
+ * - being a JSON serialisable object
25
+ * - filtering out unnecessary nodes
26
+ * - limiting the depth of nodes returned from the root
27
+ */
28
+ export function buildJsonGraph<T>(
29
+ graph: ContentGraph<Node>,
30
+ rootNodeId: string | null | undefined,
31
+ filter: (node: Node) => boolean = () => true,
32
+ extra?: (node: Node) => T,
33
+ ): JsonGraph<T> {
34
+ const depths = new Map<number, number>();
35
+
36
+ const rootNodeIndex = rootNodeId
37
+ ? graph.getNodeIdByContentKey(rootNodeId)
38
+ : graph.rootNodeId;
39
+
40
+ // build path from `graph.rootNodeId` node to `rootNodeId`
41
+ let path: number[] | null = null;
42
+ {
43
+ const stack: number[] = [];
44
+ const visited = new Set<number>();
45
+ graph.dfs({
46
+ visit: {
47
+ enter(nodeId: number, context: any, actions: {stop: () => void}) {
48
+ stack.push(nodeId);
49
+ visited.add(nodeId);
50
+
51
+ if (nodeId === rootNodeIndex) {
52
+ actions.stop();
53
+ path = stack.slice();
54
+ }
55
+ },
56
+ exit() {
57
+ stack.pop();
58
+ },
59
+ },
60
+ getChildren: (nodeId: number) => {
61
+ return graph.getNodeIdsConnectedFrom(nodeId, ALL_EDGE_TYPES);
62
+ },
63
+ startNodeId: graph.rootNodeId,
64
+ });
65
+ }
66
+
67
+ // Build depths of all nodes in the graph
68
+ {
69
+ const topologicalOrder: number[] = [];
70
+ graph.traverse(
71
+ {
72
+ exit(nodeId: number) {
73
+ topologicalOrder.push(nodeId);
74
+ },
75
+ },
76
+ rootNodeIndex,
77
+ ALL_EDGE_TYPES,
78
+ );
79
+ topologicalOrder.reverse();
80
+
81
+ for (let i = 0; i < topologicalOrder.length; i++) {
82
+ const nodeId = topologicalOrder[i];
83
+ const currentDepth = depths.get(nodeId) ?? 0;
84
+ const neighbors = graph.getNodeIdsConnectedFrom(nodeId, ALL_EDGE_TYPES);
85
+ for (const other of neighbors) {
86
+ const increment = filter(graph.getNode(other)) ? 1 : 0;
87
+ depths.set(
88
+ other,
89
+ Math.max(currentDepth + increment, depths.get(other) ?? 0),
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ const allNodes: string[] = [];
96
+ graph.traverse(
97
+ (nodeId: any) => {
98
+ allNodes.push(nodeId);
99
+ },
100
+ rootNodeIndex,
101
+ ALL_EDGE_TYPES,
102
+ );
103
+
104
+ const maxDepth = 100;
105
+ return {
106
+ nodes: allNodes
107
+ .filter((nodeId: any) => (depths.get(nodeId) ?? 0) < maxDepth)
108
+ .filter((nodeId: any) => filter(graph.getNode(nodeId)))
109
+ .map((nodeId: any) => ({
110
+ id: graph.getNode(nodeId).id,
111
+ nodeId,
112
+ path:
113
+ nodeId === rootNodeIndex
114
+ ? (path?.map((id: any) => graph.getNode(id).id) ?? null)
115
+ : null,
116
+ displayName: getDisplayName(graph.getNode(nodeId)),
117
+ level: depths.get(nodeId) ?? 0,
118
+ edges: graph
119
+ .getNodeIdsConnectedFrom(nodeId, ALL_EDGE_TYPES)
120
+ .map((nodeId) => graph.getNode(nodeId).id),
121
+ extra: extra ? extra(graph.getNode(nodeId)) : null,
122
+ })),
123
+ };
124
+ }