@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,228 @@
1
+ import assert from 'assert';
2
+ import * as sinon from 'sinon';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import childProcess from 'child_process';
6
+ import {findProjectRoot, findSourceCodeURL} from './findSourceCodeUrl';
7
+
8
+ describe('findSourceCodeUrl', function () {
9
+ let sandbox: sinon.SinonSandbox;
10
+
11
+ beforeEach(() => {
12
+ sandbox = sinon.createSandbox();
13
+ });
14
+
15
+ afterEach(() => {
16
+ sandbox.restore();
17
+ });
18
+
19
+ describe('findProjectRoot', function () {
20
+ it('should find project root when .git directory exists', function () {
21
+ const mockTarget = '/path/to/project/subdir';
22
+ const mockResolvedPath = '/resolved/path/to/project/subdir';
23
+ const mockProjectRoot = '/resolved/path/to/project';
24
+
25
+ sandbox.stub(path, 'resolve').returns(mockResolvedPath);
26
+ sandbox.stub(path, 'dirname').returns(mockProjectRoot);
27
+ sandbox.stub(path, 'join').returns('/resolved/path/to/project/.git');
28
+ sandbox.stub(fs, 'existsSync').returns(true);
29
+
30
+ const result = findProjectRoot(mockTarget);
31
+
32
+ assert.equal(result, mockResolvedPath);
33
+ assert(
34
+ (path.resolve as sinon.SinonStub).calledWith(process.cwd(), mockTarget),
35
+ );
36
+ assert(
37
+ (fs.existsSync as sinon.SinonStub).calledWith(
38
+ '/resolved/path/to/project/.git',
39
+ ),
40
+ );
41
+ });
42
+
43
+ it('should return null when no .git directory is found', function () {
44
+ const mockTarget = '/path/to/project';
45
+ const mockResolvedPath = '/resolved/path/to/project';
46
+
47
+ sandbox.stub(path, 'resolve').returns(mockResolvedPath);
48
+ sandbox.stub(path, 'dirname').callsFake((p: string) => {
49
+ if (p === mockResolvedPath) return '/resolved/path/to';
50
+ if (p === '/resolved/path/to') return '/resolved/path';
51
+ if (p === '/resolved/path') return '/resolved';
52
+ if (p === '/resolved') return '/';
53
+ return '/';
54
+ });
55
+ sandbox
56
+ .stub(path, 'join')
57
+ .callsFake((p1: string, p2: string) => `${p1}/${p2}`);
58
+ sandbox.stub(fs, 'existsSync').returns(false);
59
+
60
+ const result = findProjectRoot(mockTarget);
61
+
62
+ assert.equal(result, null);
63
+ });
64
+
65
+ it('should traverse up directories until finding .git', function () {
66
+ const mockTarget = '/path/to/project/deep/subdir';
67
+ const mockResolvedPath = '/resolved/path/to/project/deep/subdir';
68
+
69
+ sandbox.stub(path, 'resolve').returns(mockResolvedPath);
70
+ sandbox.stub(path, 'dirname').callsFake((p: string) => {
71
+ if (p === mockResolvedPath) return '/resolved/path/to/project/deep';
72
+ if (p === '/resolved/path/to/project/deep')
73
+ return '/resolved/path/to/project';
74
+ if (p === '/resolved/path/to/project') return '/resolved/path/to';
75
+ return '/';
76
+ });
77
+ sandbox
78
+ .stub(path, 'join')
79
+ .callsFake((p1: string, _p2: string) => `${p1}/.git`);
80
+
81
+ const existsStub = sandbox.stub(fs, 'existsSync');
82
+ existsStub
83
+ .withArgs('/resolved/path/to/project/deep/subdir/.git')
84
+ .returns(false);
85
+ existsStub.withArgs('/resolved/path/to/project/deep/.git').returns(false);
86
+ existsStub.withArgs('/resolved/path/to/project/.git').returns(true);
87
+
88
+ const result = findProjectRoot(mockTarget);
89
+
90
+ assert.equal(result, '/resolved/path/to/project');
91
+ });
92
+ });
93
+
94
+ describe('findSourceCodeURL', function () {
95
+ it('should return null when no project root is found', function () {
96
+ const mockTarget = '/path/to/project';
97
+
98
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
99
+ sandbox.stub(path, 'dirname').returns('/');
100
+ sandbox.stub(path, 'join').returns('/.git');
101
+ sandbox.stub(fs, 'existsSync').returns(false);
102
+
103
+ const result = findSourceCodeURL(mockTarget);
104
+
105
+ assert.equal(result, null);
106
+ });
107
+
108
+ it('should parse GitHub SSH URL correctly', function () {
109
+ const mockTarget = '/path/to/project';
110
+
111
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
112
+ sandbox.stub(path, 'dirname').returns('/');
113
+ sandbox.stub(path, 'join').returns('/resolved/path/.git');
114
+ sandbox.stub(fs, 'existsSync').returns(true);
115
+
116
+ const mockGitOutput =
117
+ 'origin\tgit@github.com:owner/repo.git (fetch)\norigin\tgit@github.com:owner/repo.git (push)';
118
+ sandbox
119
+ .stub(childProcess, 'execSync')
120
+ .returns(Buffer.from(mockGitOutput));
121
+
122
+ const result = findSourceCodeURL(mockTarget);
123
+
124
+ assert.deepEqual(result, {
125
+ type: 'github',
126
+ owner: 'owner',
127
+ repo: 'repo',
128
+ });
129
+ });
130
+
131
+ it('should parse GitHub HTTPS URL correctly', function () {
132
+ const mockTarget = '/path/to/project';
133
+
134
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
135
+ sandbox.stub(path, 'dirname').returns('/');
136
+ sandbox.stub(path, 'join').returns('/resolved/path/.git');
137
+ sandbox.stub(fs, 'existsSync').returns(true);
138
+
139
+ const mockGitOutput = 'origin\thttps://github.com/owner/repo.git (fetch)';
140
+ sandbox
141
+ .stub(childProcess, 'execSync')
142
+ .returns(Buffer.from(mockGitOutput));
143
+
144
+ const result = findSourceCodeURL(mockTarget);
145
+
146
+ assert.deepEqual(result, {
147
+ type: 'github',
148
+ owner: 'owner',
149
+ repo: 'repo',
150
+ });
151
+ });
152
+
153
+ it('should parse Bitbucket SSH URL correctly', function () {
154
+ const mockTarget = '/path/to/project';
155
+
156
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
157
+ sandbox.stub(path, 'dirname').returns('/');
158
+ sandbox.stub(path, 'join').returns('/resolved/path/.git');
159
+ sandbox.stub(fs, 'existsSync').returns(true);
160
+
161
+ const mockGitOutput = 'origin\tgit@bitbucket.org:owner/repo.git (fetch)';
162
+ sandbox
163
+ .stub(childProcess, 'execSync')
164
+ .returns(Buffer.from(mockGitOutput));
165
+
166
+ const result = findSourceCodeURL(mockTarget);
167
+
168
+ assert.deepEqual(result, {
169
+ type: 'bitbucket',
170
+ owner: 'owner',
171
+ repo: 'repo',
172
+ });
173
+ });
174
+
175
+ it('should return null when no valid remote is found', function () {
176
+ const mockTarget = '/path/to/project';
177
+
178
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
179
+ sandbox.stub(path, 'dirname').returns('/');
180
+ sandbox.stub(path, 'join').returns('/resolved/path/.git');
181
+ sandbox.stub(fs, 'existsSync').returns(true);
182
+
183
+ const mockGitOutput = 'origin\tgit@example.com:owner/repo.git (fetch)';
184
+ sandbox
185
+ .stub(childProcess, 'execSync')
186
+ .returns(Buffer.from(mockGitOutput));
187
+
188
+ const result = findSourceCodeURL(mockTarget);
189
+
190
+ assert.equal(result, null);
191
+ });
192
+
193
+ it('should return null when git command fails', function () {
194
+ const mockTarget = '/path/to/project';
195
+
196
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
197
+ sandbox.stub(path, 'dirname').returns('/');
198
+ sandbox.stub(path, 'join').returns('/resolved/path/.git');
199
+ sandbox.stub(fs, 'existsSync').returns(true);
200
+
201
+ sandbox
202
+ .stub(childProcess, 'execSync')
203
+ .throws(new Error('Git command failed'));
204
+
205
+ assert.throws(() => {
206
+ findSourceCodeURL(mockTarget);
207
+ }, Error);
208
+ });
209
+
210
+ it('should return null when URL format is invalid', function () {
211
+ const mockTarget = '/path/to/project';
212
+
213
+ sandbox.stub(path, 'resolve').returns('/resolved/path');
214
+ sandbox.stub(path, 'dirname').returns('/');
215
+ sandbox.stub(path, 'join').returns('/resolved/path/.git');
216
+ sandbox.stub(fs, 'existsSync').returns(true);
217
+
218
+ const mockGitOutput = 'origin\tinvalid-url-format (fetch)';
219
+ sandbox
220
+ .stub(childProcess, 'execSync')
221
+ .returns(Buffer.from(mockGitOutput));
222
+
223
+ const result = findSourceCodeURL(mockTarget);
224
+
225
+ assert.equal(result, null);
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,146 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import childProcess from 'child_process';
4
+
5
+ const PROJECT_ROOT_DIRS = [
6
+ '.parcelrc',
7
+ '.atlaspackrc',
8
+ 'yarn.lock',
9
+ 'package-lock.json',
10
+ 'pnpm-lock.yaml',
11
+ ];
12
+
13
+ /**
14
+ * Finds a root directory that contains a marker directory.
15
+ *
16
+ * @param target - The target directory to search from.
17
+ * @param candidate - The marker directory to search for.
18
+ */
19
+ export function findRoot(target: string, candidate: string): string | null {
20
+ let projectRoot = path.resolve(process.cwd(), target);
21
+ let exists = false;
22
+ while (projectRoot !== '/' && projectRoot !== '.') {
23
+ if (fs.existsSync(path.join(projectRoot, candidate))) {
24
+ exists = true;
25
+ break;
26
+ }
27
+ projectRoot = path.dirname(projectRoot);
28
+ }
29
+
30
+ if (!exists) {
31
+ return null;
32
+ }
33
+
34
+ return projectRoot;
35
+ }
36
+
37
+ /**
38
+ * Finds a root directory that contains a `.git` directory.
39
+ * This is not quite matching `@atlaspack/core`'s logic.
40
+ *
41
+ * @param target - The target directory to search from.
42
+ */
43
+ export function findRepositoryRoot(target: string): string | null {
44
+ return findRoot(target, '.git');
45
+ }
46
+
47
+ /**
48
+ * Finds a root directory that contains a project root directory.
49
+ *
50
+ * @param target - The target directory to search from.
51
+ */
52
+ export function findProjectRoot(target: string): string | null {
53
+ for (const candidate of PROJECT_ROOT_DIRS) {
54
+ const root = findRoot(target, candidate);
55
+ if (root) {
56
+ return root;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * A parsed remote repository URL, for either GitHub or BitBucket repositories.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * // github.com/owner/repo
68
+ * {
69
+ * owner: 'owner',
70
+ * repo: 'repo',
71
+ * type: 'github',
72
+ * }
73
+ * ```
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * // bitbucket.org/owner/repo
78
+ * {
79
+ * owner: 'owner',
80
+ * repo: 'repo',
81
+ * type: 'bitbucket',
82
+ * }
83
+ * ```
84
+ */
85
+ export interface SourceCodeURL {
86
+ owner: string;
87
+ repo: string;
88
+ type: 'github' | 'bitbucket';
89
+ }
90
+
91
+ /**
92
+ * Based on the directory path, find a source code URL for this project.
93
+ *
94
+ * This is based on parsing `git remote` URLs.
95
+ *
96
+ * Both SSH and HTTP URLs should be supported for both GitHub and BitBucket
97
+ * Cloud.
98
+ *
99
+ * If a repository has multiple remotes, the first GitHub/BitBucket remote
100
+ * will be used.
101
+ *
102
+ * This might not work for repositories using BitBucket Server, or mirror
103
+ * URLs as remotes.
104
+ */
105
+ export function findSourceCodeURL(target: string): SourceCodeURL | null {
106
+ const repositoryRoot = findRepositoryRoot(target);
107
+ const projectRoot = findProjectRoot(target);
108
+
109
+ if (!repositoryRoot || !projectRoot) {
110
+ return null;
111
+ }
112
+
113
+ const remotes = childProcess
114
+ .execSync('git remote -v', {
115
+ cwd: repositoryRoot,
116
+ })
117
+ .toString()
118
+ .split('\n')
119
+ .filter(Boolean)
120
+ .map((line) => line.split('\t'))
121
+ .map(([name, url]) => ({name, url}));
122
+
123
+ const remote = remotes.find(
124
+ ({url}) => url.includes('bitbucket.org') || url.includes('github.com'),
125
+ )?.url;
126
+
127
+ if (!remote) {
128
+ return null;
129
+ }
130
+
131
+ const remoteUrl = remote.split(' ')[0];
132
+ const type = remoteUrl.includes('bitbucket.org') ? 'bitbucket' : 'github';
133
+
134
+ // get owner and repo from HTTP or SSH urls
135
+ const regex =
136
+ /^(?:https?:\/\/|git@)(github|bitbucket)\.(com|org)[:/]([^/\s.]+)\/([^/\s.]+)(\.git)?/;
137
+ const match = remoteUrl.match(regex);
138
+
139
+ if (!match) {
140
+ return null;
141
+ }
142
+
143
+ const [, , , owner, repo] = match;
144
+
145
+ return {type, owner, repo};
146
+ }
@@ -0,0 +1,169 @@
1
+ import assert from 'assert';
2
+ import * as sinon from 'sinon';
3
+ import {getCacheStats} from './getCacheStats';
4
+
5
+ describe('getCacheStats', function () {
6
+ let sandbox: sinon.SinonSandbox;
7
+ let mockCache: any;
8
+
9
+ beforeEach(() => {
10
+ sandbox = sinon.createSandbox();
11
+ mockCache = {
12
+ keys: sandbox.stub(),
13
+ getBlobSync: sandbox.stub(),
14
+ };
15
+ });
16
+
17
+ afterEach(() => {
18
+ sandbox.restore();
19
+ });
20
+
21
+ it('should return empty stats for empty cache', function () {
22
+ mockCache.keys.returns([]);
23
+
24
+ const result = getCacheStats(mockCache);
25
+
26
+ assert.deepEqual(result, {
27
+ size: 0,
28
+ count: 0,
29
+ keySize: 0,
30
+ assetContentCount: 0,
31
+ assetContentSize: 0,
32
+ assetMapCount: 0,
33
+ assetMapSize: 0,
34
+ });
35
+ });
36
+
37
+ it('should calculate stats for cache with regular keys', function () {
38
+ const mockKeys = ['key1', 'key2', 'key3'];
39
+ const mockValues = [
40
+ Buffer.from('value1'),
41
+ Buffer.from('value2'),
42
+ Buffer.from('value3'),
43
+ ];
44
+
45
+ mockCache.keys.returns(mockKeys);
46
+ mockCache.getBlobSync.onCall(0).returns(mockValues[0]);
47
+ mockCache.getBlobSync.onCall(1).returns(mockValues[1]);
48
+ mockCache.getBlobSync.onCall(2).returns(mockValues[2]);
49
+
50
+ const result = getCacheStats(mockCache);
51
+
52
+ const expectedSize =
53
+ mockValues[0].length + mockValues[1].length + mockValues[2].length;
54
+ const expectedKeySize =
55
+ Buffer.from('key1').length +
56
+ Buffer.from('key2').length +
57
+ Buffer.from('key3').length;
58
+
59
+ assert.equal(result.size, expectedSize);
60
+ assert.equal(result.count, 3);
61
+ assert.equal(result.keySize, expectedKeySize);
62
+ assert.equal(result.assetContentCount, 0);
63
+ assert.equal(result.assetContentSize, 0);
64
+ assert.equal(result.assetMapCount, 0);
65
+ assert.equal(result.assetMapSize, 0);
66
+ });
67
+
68
+ it('should count asset content keys correctly', function () {
69
+ const mockKeys = ['asset1:content', 'asset2:content', 'other-key'];
70
+ const mockValues = [
71
+ Buffer.from('content1'),
72
+ Buffer.from('content2'),
73
+ Buffer.from('other'),
74
+ ];
75
+
76
+ mockCache.keys.returns(mockKeys);
77
+ mockCache.getBlobSync.onCall(0).returns(mockValues[0]);
78
+ mockCache.getBlobSync.onCall(1).returns(mockValues[1]);
79
+ mockCache.getBlobSync.onCall(2).returns(mockValues[2]);
80
+
81
+ const result = getCacheStats(mockCache);
82
+
83
+ assert.equal(result.assetContentCount, 2);
84
+ assert.equal(
85
+ result.assetContentSize,
86
+ mockValues[0].length + mockValues[1].length,
87
+ );
88
+ });
89
+
90
+ it('should count asset map keys correctly', function () {
91
+ const mockKeys = ['asset1:map', 'asset2:map', 'other-key'];
92
+ const mockValues = [
93
+ Buffer.from('map1'),
94
+ Buffer.from('map2'),
95
+ Buffer.from('other'),
96
+ ];
97
+
98
+ mockCache.keys.returns(mockKeys);
99
+ mockCache.getBlobSync.onCall(0).returns(mockValues[0]);
100
+ mockCache.getBlobSync.onCall(1).returns(mockValues[1]);
101
+ mockCache.getBlobSync.onCall(2).returns(mockValues[2]);
102
+
103
+ const result = getCacheStats(mockCache);
104
+
105
+ assert.equal(result.assetMapCount, 2);
106
+ assert.equal(
107
+ result.assetMapSize,
108
+ mockValues[0].length + mockValues[1].length,
109
+ );
110
+ });
111
+
112
+ it('should handle mixed cache keys correctly', function () {
113
+ const mockKeys = [
114
+ 'asset1:content',
115
+ 'asset2:map',
116
+ 'regular-key',
117
+ 'asset3:content',
118
+ 'asset4:map',
119
+ ];
120
+ const mockValues = [
121
+ Buffer.from('content1'),
122
+ Buffer.from('map1'),
123
+ Buffer.from('regular'),
124
+ Buffer.from('content2'),
125
+ Buffer.from('map2'),
126
+ ];
127
+
128
+ mockCache.keys.returns(mockKeys);
129
+ mockValues.forEach((value, index) => {
130
+ mockCache.getBlobSync.onCall(index).returns(value);
131
+ });
132
+
133
+ const result = getCacheStats(mockCache);
134
+
135
+ const totalSize = mockValues.reduce((sum, val) => sum + val.length, 0);
136
+ const totalKeySize = mockKeys.reduce(
137
+ (sum, key) => sum + Buffer.from(key).length,
138
+ 0,
139
+ );
140
+
141
+ assert.equal(result.size, totalSize);
142
+ assert.equal(result.count, 5);
143
+ assert.equal(result.keySize, totalKeySize);
144
+ assert.equal(result.assetContentCount, 2);
145
+ assert.equal(
146
+ result.assetContentSize,
147
+ mockValues[0].length + mockValues[3].length,
148
+ );
149
+ assert.equal(result.assetMapCount, 2);
150
+ assert.equal(
151
+ result.assetMapSize,
152
+ mockValues[1].length + mockValues[4].length,
153
+ );
154
+ });
155
+
156
+ it('should handle empty string keys', function () {
157
+ const mockKeys = [''];
158
+ const mockValues = [Buffer.from('')];
159
+
160
+ mockCache.keys.returns(mockKeys);
161
+ mockCache.getBlobSync.onCall(0).returns(mockValues[0]);
162
+
163
+ const result = getCacheStats(mockCache);
164
+
165
+ assert.equal(result.size, 0);
166
+ assert.equal(result.count, 1);
167
+ assert.equal(result.keySize, 0);
168
+ });
169
+ });
@@ -0,0 +1,46 @@
1
+ import {LMDBLiteCache} from '@atlaspack/cache';
2
+
3
+ export interface CacheStats {
4
+ size: number;
5
+ count: number;
6
+ keySize: number;
7
+ assetContentCount: number;
8
+ assetContentSize: number;
9
+ assetMapCount: number;
10
+ assetMapSize: number;
11
+ }
12
+
13
+ /**
14
+ * Aggregate data based on general cache usage, by reading all entries
15
+ * in the cache.
16
+ *
17
+ * This can take a non-neglible amount of time on large caches, but
18
+ * will still be a lot faster than the graph deserialization steps.
19
+ */
20
+ export function getCacheStats(cache: LMDBLiteCache): CacheStats {
21
+ const stats: CacheStats = {
22
+ size: 0,
23
+ count: 0,
24
+ keySize: 0,
25
+ assetContentCount: 0,
26
+ assetContentSize: 0,
27
+ assetMapCount: 0,
28
+ assetMapSize: 0,
29
+ };
30
+
31
+ for (const key of cache.keys()) {
32
+ const value = cache.getBlobSync(key);
33
+ stats.size += value.length;
34
+ stats.keySize += Buffer.from(key).length;
35
+ stats.count++;
36
+ if (key.endsWith(':content')) {
37
+ stats.assetContentCount++;
38
+ stats.assetContentSize += value.length;
39
+ } else if (key.endsWith(':map')) {
40
+ stats.assetMapCount++;
41
+ stats.assetMapSize += value.length;
42
+ }
43
+ }
44
+
45
+ return stats;
46
+ }
@@ -0,0 +1,84 @@
1
+ import assert from 'assert';
2
+ import {getDisplayName} from './getDisplayName';
3
+
4
+ describe('getDisplayName', function () {
5
+ it('should return display name for asset node', function () {
6
+ const assetNode = {
7
+ type: 'asset',
8
+ value: {
9
+ filePath: '/path/to/file.js',
10
+ },
11
+ id: 'asset1',
12
+ };
13
+
14
+ const result = getDisplayName(assetNode);
15
+
16
+ assert.equal(result, 'asset: /path/to/file.js');
17
+ });
18
+
19
+ it('should return display name for dependency node', function () {
20
+ const dependencyNode = {
21
+ type: 'dependency',
22
+ value: {
23
+ specifier: './module',
24
+ },
25
+ id: 'dep1',
26
+ };
27
+
28
+ const result = getDisplayName(dependencyNode);
29
+
30
+ assert.equal(result, "dependency: import './module'");
31
+ });
32
+
33
+ it('should return display name for asset_group node', function () {
34
+ const assetGroupNode = {
35
+ type: 'asset_group',
36
+ value: {
37
+ filePath: '/path/to/asset-group.js',
38
+ },
39
+ id: 'group1',
40
+ };
41
+
42
+ const result = getDisplayName(assetGroupNode);
43
+
44
+ assert.equal(result, 'asset group: /path/to/asset-group.js');
45
+ });
46
+
47
+ it('should return display name for bundle node', function () {
48
+ const bundleNode = {
49
+ type: 'bundle',
50
+ value: {
51
+ displayName: 'main.js',
52
+ },
53
+ id: 'bundle1',
54
+ };
55
+
56
+ const result = getDisplayName(bundleNode);
57
+
58
+ assert.equal(result, 'bundle: main.js');
59
+ });
60
+
61
+ it('should return node id for unknown node type', function () {
62
+ const unknownNode = {
63
+ type: 'unknown',
64
+ value: {},
65
+ id: 'unknown123',
66
+ };
67
+
68
+ const result = getDisplayName(unknownNode);
69
+
70
+ assert.equal(result, 'unknown123');
71
+ });
72
+
73
+ it('should return node id when value is null', function () {
74
+ const nodeWithNullValue = {
75
+ type: 'unknown_type',
76
+ value: null,
77
+ id: 'null-asset',
78
+ };
79
+
80
+ const result = getDisplayName(nodeWithNullValue);
81
+
82
+ assert.equal(result, 'null-asset');
83
+ });
84
+ });
@@ -0,0 +1,20 @@
1
+ /* eslint-disable monorepo/no-internal-import */
2
+ // @ts-expect-error TS2749
3
+ import type {Node} from '@atlaspack/core/lib/types.js';
4
+
5
+ export function getDisplayName(node: Node): string {
6
+ if (node.type === 'asset') {
7
+ return `asset: ${node.value.filePath}`;
8
+ }
9
+ if (node.type === 'dependency') {
10
+ return `dependency: import '${node.value.specifier}'`;
11
+ }
12
+ if (node.type === 'asset_group') {
13
+ return `asset group: ${node.value.filePath}`;
14
+ }
15
+ if (node.type === 'bundle') {
16
+ return `bundle: ${node.value.displayName}`;
17
+ }
18
+
19
+ return node.id;
20
+ }