pkgviz 0.7.7 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -1
- package/.next/build-manifest.json +15 -24
- package/.next/fallback-build-manifest.json +15 -0
- package/.next/images-manifest.json +14 -3
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +52 -16
- package/.next/required-server-files.js +349 -0
- package/.next/required-server-files.json +106 -75
- package/.next/routes-manifest.json +16 -6
- package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
- package/.next/server/app/_global-error/page/build-manifest.json +20 -0
- package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
- package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
- package/.next/server/app/_global-error/page.js +9 -0
- package/.next/server/app/_global-error/page.js.map +5 -0
- package/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
- package/.next/server/app/_global-error.html +1 -0
- package/.next/server/app/_global-error.meta +22 -0
- package/.next/server/app/_global-error.rsc +15 -0
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +20 -0
- package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -0
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
- package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
- package/.next/server/app/_not-found/page/build-manifest.json +20 -0
- package/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
- package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
- package/.next/server/app/_not-found/page.js +13 -2
- package/.next/server/app/_not-found/page.js.map +5 -0
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.meta +19 -0
- package/.next/server/app/_not-found.rsc +18 -19
- package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -0
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +24 -0
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
- package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
- package/.next/server/app/favicon.ico/route/build-manifest.json +12 -0
- package/.next/server/app/favicon.ico/route.js +6 -1
- package/.next/server/app/favicon.ico/route.js.map +5 -0
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/page/app-paths-manifest.json +3 -0
- package/.next/server/app/page/build-manifest.json +20 -0
- package/.next/server/app/page/next-font-manifest.json +11 -0
- package/.next/server/app/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/page/server-reference-manifest.json +93 -0
- package/.next/server/app/page.js +16 -13
- package/.next/server/app/page.js.map +5 -0
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +3 -1
- package/.next/server/app-paths-manifest.json +2 -1
- package/.next/server/chunks/[externals]__0l8ei7u._.js +3 -0
- package/.next/server/chunks/[externals]__0l8ei7u._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__1to-yfd._.js +160 -0
- package/.next/server/chunks/[root-of-the-server]__1to-yfd._.js.map +1 -0
- package/.next/server/chunks/[turbopack]_runtime.js +807 -0
- package/.next/server/chunks/[turbopack]_runtime.js.map +10 -0
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjls.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjls.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0agsrep._.js +28 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0agsrep._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0f_e7c8._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0f_e7c8._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0yqn68e._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0yqn68e._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__112mz-6._.js +14 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__112mz-6._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__11_76b7._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__11_76b7._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__12ovu0c._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__12ovu0c._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__16pmw__._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__16pmw__._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1f2jx51._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1f2jx51._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1rn09vt._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1rn09vt._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__20e4gkp._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__20e4gkp._.js.map +1 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js +807 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +10 -0
- package/.next/server/chunks/ssr/_1acy_2s._.js +158 -0
- package/.next/server/chunks/ssr/_1acy_2s._.js.map +1 -0
- package/.next/server/chunks/ssr/_1rh2tf9._.js +367 -0
- package/.next/server/chunks/ssr/_1rh2tf9._.js.map +1 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-.js +3 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-.js.map +1 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yr.js +3 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yr.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_01xbj_9._.js +33 -0
- package/.next/server/chunks/ssr/node_modules_01xbj_9._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_1c5ky4f._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_1c5ky4f._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +6 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j3._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j3._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0x.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0x.js.map +1 -0
- package/.next/server/chunks/ssr/src_app_loading_tsx_01g_-m0._.js +3 -0
- package/.next/server/chunks/ssr/src_app_loading_tsx_01g_-m0._.js.map +1 -0
- package/.next/server/middleware-build-manifest.js +24 -1
- package/.next/server/middleware-manifest.json +2 -2
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +15 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages-manifest.json +2 -4
- package/.next/server/prefetch-hints.json +25 -0
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +94 -1
- package/.next/static/chunks/19mx3mg6lkumu.js +1 -0
- package/.next/static/chunks/1las_6j17xa9p.js +1 -0
- package/.next/static/chunks/1xk6snnh6e5_j.css +3 -0
- package/.next/static/chunks/2-rtuqsgzmno4.js +22 -0
- package/.next/static/chunks/27t_qfc-3_lzs.js +1 -0
- package/.next/static/chunks/2sswp23h6zh08.js +345 -0
- package/.next/static/chunks/310vm2bl3xxpt.js +31 -0
- package/.next/static/chunks/3fntmmi971322.js +1 -0
- package/.next/static/chunks/turbopack-1b6uko1cu_h42.js +1 -0
- package/.next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
- package/.next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
- package/.next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
- package/.next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
- package/.next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
- package/.next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
- package/.next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
- package/.next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
- package/.next/static/media/favicon.2246eezznj2ab.ico +0 -0
- package/.next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
- package/.next/static/rjdzHqzPw2GCNhDbjcu0-/_buildManifest.js +11 -0
- package/.next/static/rjdzHqzPw2GCNhDbjcu0-/_clientMiddlewareManifest.js +1 -0
- package/README.md +176 -4
- package/bin/pkgviz.ts +57 -29
- package/package.json +57 -36
- package/src/app/actions/audit.actions.ts +7 -49
- package/src/app/globals.css +0 -2
- package/src/app/layout.tsx +3 -2
- package/src/app/loading.tsx +8 -0
- package/src/app/page.tsx +16 -2
- package/src/app/utils/buildGraph.ts +99 -100
- package/src/app/utils/getParsedFileStructure.ts +128 -54
- package/src/app/utils/inspectParserLanguageAsync.ts +39 -0
- package/src/app/utils/markCyclicPackages.ts +45 -185
- package/src/app/utils/parser/cpp/parseCppFile.ts +68 -86
- package/src/app/utils/parser/delphi/parseFile.ts +36 -120
- package/src/app/utils/parser/java/findEntryPoint.ts +2 -1
- package/src/app/utils/parser/java/parseJavaFile.ts +33 -45
- package/src/app/utils/parser/kotlin/parseFile.ts +34 -87
- package/src/app/utils/parser/python/parseFile.ts +30 -100
- package/src/app/utils/parser/typescript/parseFile.ts +17 -52
- package/src/app/utils/selectParserLanguage.ts +39 -0
- package/src/cli/formatAuditRuleFailures.ts +21 -0
- package/src/cli/runAuditAsync.ts +41 -0
- package/src/components/Breadcrumb.tsx +5 -4
- package/src/components/ExportPanel.tsx +53 -0
- package/src/components/Header.tsx +1 -0
- package/src/components/Loader.tsx +8 -2
- package/src/components/ProjectLoadError.tsx +15 -0
- package/src/components/SettingsPanel.tsx +184 -0
- package/src/components/Switch.tsx +9 -17
- package/src/components/ThemeToggle.tsx +2 -1
- package/src/components/ToggleSwitch.tsx +36 -0
- package/src/components/sidebar/Sidebar.tsx +14 -0
- package/src/components/sidebar/SidebarBadge.tsx +25 -0
- package/src/components/sidebar/SidebarRow.tsx +14 -0
- package/src/components/sidebar/SidebarSection.tsx +16 -0
- package/src/components/sidebar/SidebarTabs.tsx +58 -0
- package/src/contexts/SettingsContext.tsx +19 -20
- package/src/features/audit/adapters/inbound/react/AuditRuleList.tsx +76 -0
- package/src/features/audit/adapters/inbound/react/AuditRulePanel.tsx +30 -0
- package/src/features/audit/adapters/inbound/react/CycleInspector.tsx +116 -0
- package/src/features/audit/adapters/inbound/react/CyclicDependenciesRuleDetails.tsx +124 -0
- package/src/features/audit/adapters/inbound/react/useCycleSelection.ts +47 -0
- package/src/features/audit/application/use-cases/createAuditFromSnapshot.ts +28 -0
- package/src/features/audit/application/use-cases/resolveSelectedCycleIds.ts +18 -0
- package/src/features/audit/composition/createAuditAsync.ts +11 -0
- package/src/features/audit/domain/evaluateAuditRules.ts +42 -0
- package/src/features/audit/domain/hasBlockingAuditRuleFailure.ts +6 -0
- package/src/features/audit/domain/resolveAuditConfiguration.ts +27 -0
- package/src/features/audit/utils/cycleVisualization.ts +86 -0
- package/src/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeDependencyImportsAsync.ts +172 -0
- package/src/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeTypeScriptImportsAsync.ts +6 -0
- package/src/features/graph-view/adapters/inbound/react/DependencyGraphView.tsx +184 -0
- package/src/features/graph-view/adapters/inbound/react/GraphZoomControls.tsx +48 -0
- package/src/features/graph-view/adapters/inbound/react/applyGraphInteractionPresentation.ts +35 -0
- package/src/features/graph-view/adapters/inbound/react/createGraphViewModel.ts +240 -0
- package/src/features/graph-view/adapters/inbound/react/createGraphViewStyles.ts +37 -0
- package/src/features/graph-view/adapters/inbound/react/useGraphFocus.ts +104 -0
- package/src/features/graph-view/adapters/inbound/react/useGraphInteractions.ts +50 -0
- package/src/features/graph-view/adapters/inbound/react/useGraphProjection.ts +71 -0
- package/src/features/graph-view/adapters/inbound/react/useGraphViewport.ts +48 -0
- package/src/features/graph-view/domain/getDependencyHighlightClasses.ts +39 -0
- package/src/features/graph-view/domain/reduceGraphInteraction.ts +23 -0
- package/src/features/graph-view/utils/createCompoundOpacityIndex.ts +38 -0
- package/src/features/graph-view/utils/projectVisibleGraph.ts +94 -0
- package/src/features/graph-view/utils/readNodeDefinitionId.ts +7 -0
- package/src/features/graph-view/utils/removeEmptyStructuralNodes.ts +27 -0
- package/src/features/project-analysis/adapters/outbound/filesystem/readProjectSnapshotAsync.ts +12 -0
- package/src/features/project-analysis/application/use-cases/createProjectSnapshotReader.ts +29 -0
- package/src/features/project-analysis/composition/loadProjectOverviewAsync.ts +14 -0
- package/src/features/project-analysis/composition/loadProjectSnapshotAsync.ts +12 -0
- package/src/features/project-tree/adapters/inbound/react/ProjectTreePanel.tsx +52 -0
- package/src/features/project-tree/adapters/inbound/react/useProjectTreeExpansion.ts +46 -0
- package/src/features/project-tree/application/use-cases/buildProjectTree.ts +61 -0
- package/src/features/project-tree/application/use-cases/resolveProjectTreeNavigation.ts +13 -0
- package/src/features/project-tree/utils/findProjectTreeNode.ts +14 -0
- package/src/features/project-tree/utils/findProjectTreeNodeByGraphPackage.ts +21 -0
- package/src/features/project-tree/utils/getProjectTreeAncestorIds.ts +26 -0
- package/src/features/settings/utils/readSettingsEnvironment.ts +53 -0
- package/src/i18n/en.ts +21 -2
- package/src/i18n/i18n.ts +2 -3
- package/src/layouts/breadthfirst/style.ts +1 -0
- package/src/layouts/circle/style.ts +1 -0
- package/src/layouts/concentric/style.ts +1 -0
- package/src/layouts/elk/layout.ts +1 -3
- package/src/layouts/elk/style.ts +1 -0
- package/src/layouts/getWeightBuckets.ts +3 -1
- package/src/layouts/grid/style.ts +1 -0
- package/src/layouts/index.ts +0 -4
- package/src/layouts/style.ts +172 -25
- package/src/screens/home/Home.tsx +73 -25
- package/src/screens/home/HomeGraph.tsx +43 -0
- package/src/screens/home/HomeSidebar.tsx +129 -0
- package/src/shared/types/index.ts +0 -9
- package/src/shared/utils/getProjectName.ts +4 -1
- package/src/shared/utils/parseProjectPath.ts +1 -0
- package/src/shared/utils/toPosix.ts +1 -1
- package/src/types/audit.ts +76 -0
- package/src/types/auditVisualization.ts +22 -0
- package/src/types/graphInteraction.ts +9 -0
- package/src/types/parserSelection.ts +11 -0
- package/src/types/projectAnalysis.ts +20 -0
- package/src/types/projectAnalysisActionResult.ts +2 -0
- package/src/types/projectTree.ts +7 -0
- package/src/utils/filter/filterByPackagePrefix.ts +25 -7
- package/src/utils/filter/filterEmptyPackages.ts +28 -29
- package/src/utils/filter/filterSubPackagesFromDepth.ts +20 -17
- package/src/utils/filter/filterVendorPackages.ts +1 -1
- package/src/utils/filter/isIntrinsicGraphNode.ts +8 -0
- package/src/utils/filter/toggleCompoundNodes.ts +41 -31
- package/src/utils/runProjectAnalysisActionAsync.ts +14 -0
- package/tsconfig.json +11 -3
- package/.next/app-build-manifest.json +0 -29
- package/.next/cache/.previewinfo +0 -1
- package/.next/cache/.rscinfo +0 -1
- package/.next/cache/.tsbuildinfo +0 -1
- package/.next/cache/chrome-devtools-workspace-uuid +0 -1
- package/.next/cache/eslint/.cache_6t30dc +0 -1
- package/.next/cache/webpack/client-production/0.pack +0 -4
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/10.pack +0 -0
- package/.next/cache/webpack/client-production/11.pack +0 -0
- package/.next/cache/webpack/client-production/12.pack +0 -0
- package/.next/cache/webpack/client-production/13.pack +0 -0
- package/.next/cache/webpack/client-production/14.pack +0 -0
- package/.next/cache/webpack/client-production/15.pack +0 -0
- package/.next/cache/webpack/client-production/16.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/3.pack +0 -0
- package/.next/cache/webpack/client-production/4.pack +0 -0
- package/.next/cache/webpack/client-production/5.pack +0 -0
- package/.next/cache/webpack/client-production/6.pack +0 -0
- package/.next/cache/webpack/client-production/7.pack +0 -0
- package/.next/cache/webpack/client-production/8.pack +0 -0
- package/.next/cache/webpack/client-production/9.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/1.pack +0 -0
- package/.next/cache/webpack/server-production/10.pack +0 -4
- package/.next/cache/webpack/server-production/11.pack +0 -0
- package/.next/cache/webpack/server-production/12.pack +0 -0
- package/.next/cache/webpack/server-production/13.pack +0 -0
- package/.next/cache/webpack/server-production/14.pack +0 -0
- package/.next/cache/webpack/server-production/15.pack +0 -0
- package/.next/cache/webpack/server-production/16.pack +0 -0
- package/.next/cache/webpack/server-production/17.pack +0 -0
- package/.next/cache/webpack/server-production/18.pack +0 -4
- package/.next/cache/webpack/server-production/2.pack +0 -0
- package/.next/cache/webpack/server-production/3.pack +0 -0
- package/.next/cache/webpack/server-production/4.pack +0 -0
- package/.next/cache/webpack/server-production/5.pack +0 -0
- package/.next/cache/webpack/server-production/6.pack +0 -0
- package/.next/cache/webpack/server-production/7.pack +0 -0
- package/.next/cache/webpack/server-production/8.pack +0 -0
- package/.next/cache/webpack/server-production/9.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -7
- package/.next/server/app/index.rsc +0 -20
- package/.next/server/chunks/548.js +0 -6
- package/.next/server/chunks/610.js +0 -7
- package/.next/server/chunks/985.js +0 -22
- package/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/.next/server/pages/_app.js +0 -1
- package/.next/server/pages/_app.js.nft.json +0 -1
- package/.next/server/pages/_document.js +0 -1
- package/.next/server/pages/_document.js.nft.json +0 -1
- package/.next/server/pages/_error.js +0 -19
- package/.next/server/pages/_error.js.nft.json +0 -1
- package/.next/server/webpack-runtime.js +0 -1
- package/.next/static/_YC2p40ZiYT2Gcw-h_tjC/_buildManifest.js +0 -1
- package/.next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +0 -1
- package/.next/static/chunks/680-55ad17895c8fb70b.js +0 -1
- package/.next/static/chunks/90542734-29e255269788f1bd.js +0 -1
- package/.next/static/chunks/91dbc596-b9cdccd3f5cba95d.js +0 -1
- package/.next/static/chunks/964-b440f7af22c41ca5.js +0 -1
- package/.next/static/chunks/app/_not-found/page-11e4fea8e290f304.js +0 -1
- package/.next/static/chunks/app/layout-8aae7fba5ed8afb3.js +0 -1
- package/.next/static/chunks/app/page-91450b1230da8bca.js +0 -1
- package/.next/static/chunks/framework-bc35dade6ff9684e.js +0 -1
- package/.next/static/chunks/main-9e86d333c1e02dd1.js +0 -1
- package/.next/static/chunks/main-app-69a9ff75ff3465ca.js +0 -1
- package/.next/static/chunks/pages/_app-0a0020ddd67f79cf.js +0 -1
- package/.next/static/chunks/pages/_error-03529f2c21436739.js +0 -1
- package/.next/static/chunks/webpack-7adfafc806738767.js +0 -1
- package/.next/static/css/ec31779168107a11.css +0 -3
- package/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- package/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- package/.next/trace +0 -2
- package/.next/types/app/layout.ts +0 -84
- package/.next/types/app/page.ts +0 -84
- package/.next/types/cache-life.d.ts +0 -141
- package/.next/types/package.json +0 -1
- package/src/app/actions/graph.actions.ts +0 -25
- package/src/app/utils/parser/cpp/extractCppPackageFromImport.ts +0 -18
- package/src/app/utils/parser/delphi/extractPackageFromImport.ts +0 -21
- package/src/app/utils/parser/java/extractJavaPackageFromImport.ts +0 -39
- package/src/app/utils/parser/java/getIntrinsicPackagesRecursive.ts +0 -33
- package/src/app/utils/parser/kotlin/extractPackageFromImport.ts +0 -19
- package/src/app/utils/parser/python/extractPythonPackageFromImport.ts +0 -18
- package/src/app/utils/parser/typescript/extractTypeScriptPackageFromImport.ts +0 -18
- package/src/components/Cytoscape.tsx +0 -23
- package/src/components/Setting.tsx +0 -17
- package/src/components/Settings.tsx +0 -189
- package/src/components/ZoomInput.tsx +0 -94
- package/src/components/useCytoscape.ts +0 -343
- package/src/layouts/getLayoutStyle.ts +0 -19
- package/src/shared/utils/detectLanguage.ts +0 -255
- package/src/shared/utils/getJsonAsync.ts +0 -13
- package/src/shared/utils/parseEnv.ts +0 -91
- package/src/store/useLocalStorage.ts +0 -29
- package/src/utils/hasChildren.ts +0 -7
- /package/.next/{react-loadable-manifest.json → server/app/_global-error/page/react-loadable-manifest.json} +0 -0
- /package/.next/static/chunks/{polyfills-42372ed130431b0a.js → 0cz1d0mv5g_q7.js} +0 -0
- /package/.next/static/{_YC2p40ZiYT2Gcw-h_tjC → rjdzHqzPw2GCNhDbjcu0-}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { GraphViewEdge, GraphViewElementEvent, GraphViewNode } from '@zora/graph-view';
|
|
4
|
+
import { useMemo, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { applyGraphInteractionPresentation } from '@/features/graph-view/adapters/inbound/react/applyGraphInteractionPresentation';
|
|
7
|
+
import { reduceGraphInteraction } from '@/features/graph-view/domain/reduceGraphInteraction';
|
|
8
|
+
import type { GraphInteractionState } from '@/types/graphInteraction';
|
|
9
|
+
|
|
10
|
+
/*** Owns leaf interaction state and declarative presentation through public GraphView events. */
|
|
11
|
+
export function useGraphInteractions(
|
|
12
|
+
nodes: readonly GraphViewNode[],
|
|
13
|
+
edges: readonly GraphViewEdge[]
|
|
14
|
+
) {
|
|
15
|
+
const leafIds = useMemo(() => {
|
|
16
|
+
const parents = new Set(nodes.map(node => node.parentId));
|
|
17
|
+
return new Set(nodes.filter(node => !parents.has(node.id)).map(node => node.id));
|
|
18
|
+
}, [nodes]);
|
|
19
|
+
const [snapshot, setSnapshot] = useState<{
|
|
20
|
+
readonly leafIds: ReadonlySet<string>;
|
|
21
|
+
readonly interaction: GraphInteractionState;
|
|
22
|
+
}>({
|
|
23
|
+
leafIds,
|
|
24
|
+
interaction: { hoveredNodeId: null, selectedNodeIds: [] },
|
|
25
|
+
});
|
|
26
|
+
if (snapshot.leafIds !== leafIds) {
|
|
27
|
+
setSnapshot({
|
|
28
|
+
leafIds,
|
|
29
|
+
interaction: {
|
|
30
|
+
hoveredNodeId: null,
|
|
31
|
+
selectedNodeIds: snapshot.interaction.selectedNodeIds.filter(id => leafIds.has(id)),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const presentation = useMemo(
|
|
36
|
+
() => applyGraphInteractionPresentation(nodes, edges, snapshot.interaction),
|
|
37
|
+
[nodes, edges, snapshot.interaction]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/*** Ignores compound policy and routes only the approved hover/selection events to the reducer. */
|
|
41
|
+
const handleNodeEvent = (event: GraphViewElementEvent) => {
|
|
42
|
+
const { id, type } = event;
|
|
43
|
+
if (!leafIds.has(id) || type === 'press' || type === 'double-press') return;
|
|
44
|
+
setSnapshot(previous => {
|
|
45
|
+
const interaction = reduceGraphInteraction(previous.interaction, { id, type });
|
|
46
|
+
return interaction === previous.interaction ? previous : { leafIds, interaction };
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
return { ...presentation, handleNodeEvent };
|
|
50
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import type { ElementsDefinition } from 'cytoscape';
|
|
3
|
+
import { useEffect, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { projectVisibleGraph } from '@/features/graph-view/utils/projectVisibleGraph';
|
|
6
|
+
|
|
7
|
+
/***
|
|
8
|
+
* Projects graph inputs without changing the explicitly selected package scope.
|
|
9
|
+
* @performance
|
|
10
|
+
* Package/depth/vendor projection traverses nodes and aggregates edges. Keep it memoized on its
|
|
11
|
+
* actual inputs so unrelated UI renders do not repeat that work or replace the visible graph data.
|
|
12
|
+
* Preserve lifted-edge weights and explicit cycle scopes when optimizing; projectVisibleGraph tests
|
|
13
|
+
* cover those behaviors. Do not add a second independently maintained projection state.
|
|
14
|
+
*/
|
|
15
|
+
export function useGraphProjection(input: UseGraphProjectionInput): ElementsDefinition | null {
|
|
16
|
+
const {
|
|
17
|
+
currentPackage,
|
|
18
|
+
elements,
|
|
19
|
+
preservePackageScope,
|
|
20
|
+
setCurrentPackage,
|
|
21
|
+
setMaxSubPackageDepth,
|
|
22
|
+
setSubPackageDepth,
|
|
23
|
+
showCompoundNodes,
|
|
24
|
+
showVendorPackages,
|
|
25
|
+
subPackageDepth,
|
|
26
|
+
} = input;
|
|
27
|
+
const projection = useMemo(
|
|
28
|
+
() =>
|
|
29
|
+
elements === null
|
|
30
|
+
? null
|
|
31
|
+
: projectVisibleGraph({
|
|
32
|
+
currentPackage,
|
|
33
|
+
elements,
|
|
34
|
+
preservePackageScope,
|
|
35
|
+
showCompoundNodes,
|
|
36
|
+
showVendorPackages,
|
|
37
|
+
subPackageDepth,
|
|
38
|
+
}),
|
|
39
|
+
[
|
|
40
|
+
currentPackage,
|
|
41
|
+
elements,
|
|
42
|
+
preservePackageScope,
|
|
43
|
+
showCompoundNodes,
|
|
44
|
+
showVendorPackages,
|
|
45
|
+
subPackageDepth,
|
|
46
|
+
]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (projection === null) return;
|
|
51
|
+
setMaxSubPackageDepth(projection.maxSubPackageDepth);
|
|
52
|
+
if (projection.redirectPackage === null && subPackageDepth > projection.maxSubPackageDepth) {
|
|
53
|
+
setSubPackageDepth(projection.maxSubPackageDepth);
|
|
54
|
+
}
|
|
55
|
+
if (projection.redirectPackage !== null) setCurrentPackage(projection.redirectPackage);
|
|
56
|
+
}, [projection, setCurrentPackage, setMaxSubPackageDepth, setSubPackageDepth, subPackageDepth]);
|
|
57
|
+
|
|
58
|
+
return projection?.redirectPackage === null ? projection.elements : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface UseGraphProjectionInput {
|
|
62
|
+
readonly currentPackage: string;
|
|
63
|
+
readonly elements: ElementsDefinition | null;
|
|
64
|
+
readonly preservePackageScope?: boolean;
|
|
65
|
+
readonly setCurrentPackage: (path: string) => void;
|
|
66
|
+
readonly setMaxSubPackageDepth: (depth: number) => void;
|
|
67
|
+
readonly setSubPackageDepth: (depth: number) => void;
|
|
68
|
+
readonly showCompoundNodes: boolean;
|
|
69
|
+
readonly showVendorPackages: boolean;
|
|
70
|
+
readonly subPackageDepth: number;
|
|
71
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { GraphViewController, GraphViewLayoutName } from '@zora/graph-view';
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
/***
|
|
5
|
+
* Mirrors the owner's settled zoom bounds and coordinates intentional layout changes.
|
|
6
|
+
* @performance
|
|
7
|
+
* Viewport events only read cached controller values; ordinary zoom must never compact spacing,
|
|
8
|
+
* measure labels or rerun layout. The explicit Fit button requests the owner's bounded compaction.
|
|
9
|
+
*/
|
|
10
|
+
export function useGraphViewport(layout: GraphViewLayoutName) {
|
|
11
|
+
const [controller, setController] = useState<GraphViewController | null>(null);
|
|
12
|
+
const controllerRef = useRef<GraphViewController | null>(null);
|
|
13
|
+
const [viewport, setViewport] = useState({ zoom: 1, min: 0.5, max: 2 });
|
|
14
|
+
const previousLayoutRef = useRef(layout);
|
|
15
|
+
|
|
16
|
+
/*** Publishes only changed viewport values, including range changes without a zoom event. */
|
|
17
|
+
const synchronize = (nextController: GraphViewController) => {
|
|
18
|
+
const { zoom } = nextController.getViewport();
|
|
19
|
+
const { min, max } = nextController.getZoomRange();
|
|
20
|
+
setViewport(previous =>
|
|
21
|
+
previous.zoom === zoom && previous.min === min && previous.max === max
|
|
22
|
+
? previous
|
|
23
|
+
: { zoom, min, max }
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/*** Captures the controller before synchronizing the initial viewport. */
|
|
28
|
+
const handleReady = (nextController: GraphViewController) => {
|
|
29
|
+
controllerRef.current = nextController;
|
|
30
|
+
setController(nextController);
|
|
31
|
+
synchronize(nextController);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/*** Reads the controller synchronously, including events before React commits readiness. */
|
|
35
|
+
const handleViewportChange = () => {
|
|
36
|
+
if (controllerRef.current !== null) synchronize(controllerRef.current);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/*** Fits intentional algorithm changes and refreshes bounds after every settled layout. */
|
|
40
|
+
const handleLayoutComplete = (nextController: GraphViewController) => {
|
|
41
|
+
const layoutChanged = previousLayoutRef.current !== layout;
|
|
42
|
+
previousLayoutRef.current = layout;
|
|
43
|
+
if (layoutChanged) nextController.fit();
|
|
44
|
+
synchronize(nextController);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return { controller, handleLayoutComplete, handleReady, handleViewportChange, ...viewport };
|
|
48
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GraphEdge } from '@ankhorage/graph';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Assigns PKGViz highlight/hushed presentation to direct incoming and outgoing dependencies.
|
|
5
|
+
* @performance Classify edges once per interaction, with set membership instead of per-node scans.
|
|
6
|
+
* This is product presentation policy; generic graph traversal remains owned by @ankhorage/graph.
|
|
7
|
+
*/
|
|
8
|
+
export function getDependencyHighlightClasses(
|
|
9
|
+
nodeIds: readonly string[],
|
|
10
|
+
edges: readonly Pick<GraphEdge, 'source' | 'target'>[],
|
|
11
|
+
activeNodeIds: ReadonlySet<string>
|
|
12
|
+
) {
|
|
13
|
+
const outgoing = new Set<string>();
|
|
14
|
+
const incoming = new Set<string>();
|
|
15
|
+
const edgeClasses = edges.map(edge => {
|
|
16
|
+
const fromActive = activeNodeIds.has(edge.source);
|
|
17
|
+
const toActive = activeNodeIds.has(edge.target);
|
|
18
|
+
if (fromActive) outgoing.add(edge.target);
|
|
19
|
+
if (toActive) incoming.add(edge.source);
|
|
20
|
+
return getClasses(false, fromActive, toActive, activeNodeIds.size > 0);
|
|
21
|
+
});
|
|
22
|
+
const nodeClasses = nodeIds.map(id =>
|
|
23
|
+
getClasses(activeNodeIds.has(id), outgoing.has(id), incoming.has(id), activeNodeIds.size > 0)
|
|
24
|
+
);
|
|
25
|
+
return { nodeClasses, edgeClasses };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*** Keeps dependency direction and active-node emphasis while dimming unrelated elements. */
|
|
29
|
+
function getClasses(active: boolean, outgoing: boolean, incoming: boolean, hasFocus: boolean) {
|
|
30
|
+
if (!hasFocus) return '';
|
|
31
|
+
if (!active && !outgoing && !incoming) return 'hushed';
|
|
32
|
+
return [
|
|
33
|
+
active ? 'highlight' : '',
|
|
34
|
+
outgoing ? 'highlight-outgoer' : '',
|
|
35
|
+
incoming ? 'highlight-incomer' : '',
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(' ');
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GraphInteractionEvent, GraphInteractionState } from '@/types/graphInteraction';
|
|
2
|
+
|
|
3
|
+
/*** Tracks leaf hover separately from selection so pointer exit restores the selected neighborhood. */
|
|
4
|
+
export function reduceGraphInteraction(
|
|
5
|
+
state: GraphInteractionState,
|
|
6
|
+
event: GraphInteractionEvent
|
|
7
|
+
): GraphInteractionState {
|
|
8
|
+
if (event.type === 'pointer-enter') {
|
|
9
|
+
return state.hoveredNodeId === event.id ? state : { ...state, hoveredNodeId: event.id };
|
|
10
|
+
}
|
|
11
|
+
if (event.type === 'pointer-leave') {
|
|
12
|
+
return state.hoveredNodeId === event.id ? { ...state, hoveredNodeId: null } : state;
|
|
13
|
+
}
|
|
14
|
+
const selected = state.selectedNodeIds.includes(event.id);
|
|
15
|
+
if ((event.type === 'select') === selected) return state;
|
|
16
|
+
return {
|
|
17
|
+
...state,
|
|
18
|
+
selectedNodeIds:
|
|
19
|
+
event.type === 'select'
|
|
20
|
+
? [...state.selectedNodeIds, event.id]
|
|
21
|
+
: state.selectedNodeIds.filter(id => id !== event.id),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Prepares paint-only depth steps with a bounded cumulative tint for nested compound surfaces.
|
|
3
|
+
* @performance Memoize ancestry once per projection; never traverse parents in style callbacks.
|
|
4
|
+
*/
|
|
5
|
+
export function createCompoundOpacityIndex(
|
|
6
|
+
parents: ReadonlyMap<string, string>,
|
|
7
|
+
detached: ReadonlySet<string>
|
|
8
|
+
): ReadonlyMap<string, number> {
|
|
9
|
+
const depths = new Map<string, number>();
|
|
10
|
+
const visiting = new Set<string>();
|
|
11
|
+
|
|
12
|
+
/*** Resolves effective visual depth, accounting for detached dependency endpoints. */
|
|
13
|
+
function depth(id: string): number {
|
|
14
|
+
const cached = depths.get(id);
|
|
15
|
+
if (cached !== undefined) return cached;
|
|
16
|
+
if (visiting.has(id)) return 0;
|
|
17
|
+
visiting.add(id);
|
|
18
|
+
const parent = detached.has(id) ? undefined : parents.get(id);
|
|
19
|
+
const result = parent === undefined ? 0 : depth(parent) + 1;
|
|
20
|
+
visiting.delete(id);
|
|
21
|
+
depths.set(id, result);
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new Map(
|
|
26
|
+
[...new Set([...parents.keys(), ...parents.values()])].map(id => {
|
|
27
|
+
const level = depth(id);
|
|
28
|
+
const target = cumulativeTint(level);
|
|
29
|
+
const previous = level === 0 ? 0 : cumulativeTint(level - 1);
|
|
30
|
+
return [id, (target - previous) / (1 - previous)];
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/*** Approaches an 18% cumulative tint without letting deep nesting become an opaque blue slab. */
|
|
36
|
+
function cumulativeTint(depth: number): number {
|
|
37
|
+
return 0.04 + (0.14 * depth) / (depth + 3);
|
|
38
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ElementsDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
import { removeEmptyStructuralNodes } from '@/features/graph-view/utils/removeEmptyStructuralNodes';
|
|
4
|
+
import { filterByPackagePrefix } from '@/utils/filter/filterByPackagePrefix';
|
|
5
|
+
import { filterEmptyPackages } from '@/utils/filter/filterEmptyPackages';
|
|
6
|
+
import { filterSubPackagesByDepth, getMaxDepth } from '@/utils/filter/filterSubPackagesFromDepth';
|
|
7
|
+
import { filterVendorPackages } from '@/utils/filter/filterVendorPackages';
|
|
8
|
+
import { toggleCompoundNodes } from '@/utils/filter/toggleCompoundNodes';
|
|
9
|
+
|
|
10
|
+
/*** Projects the complete dependency graph into the explicitly selected package/depth/filter view. */
|
|
11
|
+
export function projectVisibleGraph(input: ProjectVisibleGraphInput): ProjectVisibleGraphResult {
|
|
12
|
+
const compoundFiltered = toggleCompoundNodes(
|
|
13
|
+
input.elements,
|
|
14
|
+
input.showCompoundNodes,
|
|
15
|
+
input.currentPackage
|
|
16
|
+
);
|
|
17
|
+
const packageFiltered = filterByPackagePrefix(
|
|
18
|
+
compoundFiltered,
|
|
19
|
+
input.currentPackage.replace(/\//g, '.')
|
|
20
|
+
);
|
|
21
|
+
const depthFiltered = filterSubPackagesByDepth(packageFiltered, true, input.subPackageDepth);
|
|
22
|
+
const visible = input.showVendorPackages ? depthFiltered : filterVendorPackages(depthFiltered);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
elements: labelVisibleNodes(
|
|
26
|
+
input.showCompoundNodes ? visible : removeEmptyStructuralNodes(visible),
|
|
27
|
+
input.currentPackage
|
|
28
|
+
),
|
|
29
|
+
maxSubPackageDepth: Math.max(
|
|
30
|
+
1,
|
|
31
|
+
getMaxDepth(
|
|
32
|
+
input.showVendorPackages ? packageFiltered : filterVendorPackages(packageFiltered)
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
redirectPackage: resolveRedirectPackage(input),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ProjectVisibleGraphInput {
|
|
40
|
+
readonly currentPackage: string;
|
|
41
|
+
readonly elements: ElementsDefinition;
|
|
42
|
+
readonly preservePackageScope?: boolean;
|
|
43
|
+
readonly showCompoundNodes: boolean;
|
|
44
|
+
readonly showVendorPackages: boolean;
|
|
45
|
+
readonly subPackageDepth: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ProjectVisibleGraphResult {
|
|
49
|
+
readonly elements: ElementsDefinition;
|
|
50
|
+
readonly maxSubPackageDepth: number;
|
|
51
|
+
readonly redirectPackage: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/***
|
|
55
|
+
* Skips empty package levels only when no explicit cycle scope owns the projection.
|
|
56
|
+
* @performance
|
|
57
|
+
* An active cycle can require an ancestor package as a visible node. Redirecting into that package
|
|
58
|
+
* hides it again and causes an endless focus/redirect loop, repeatedly remounting the renderer.
|
|
59
|
+
*/
|
|
60
|
+
function resolveRedirectPackage(input: ProjectVisibleGraphInput): string | null {
|
|
61
|
+
const currentPackage = input.currentPackage.replaceAll('/', '.');
|
|
62
|
+
if (input.preservePackageScope) return null;
|
|
63
|
+
|
|
64
|
+
const nextPackage = filterEmptyPackages(currentPackage, input.elements);
|
|
65
|
+
return nextPackage === currentPackage ? null : nextPackage;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*** Adds package-relative display labels without changing graph ids. */
|
|
69
|
+
function labelVisibleNodes(
|
|
70
|
+
elements: ElementsDefinition,
|
|
71
|
+
currentPackage: string
|
|
72
|
+
): ElementsDefinition {
|
|
73
|
+
const normalizedPackage = currentPackage.replaceAll('/', '.');
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
nodes: elements.nodes.map(node => ({
|
|
77
|
+
group: 'nodes',
|
|
78
|
+
classes: node.classes ?? '',
|
|
79
|
+
data: {
|
|
80
|
+
...node.data,
|
|
81
|
+
label: getRelativeNodeLabel(String(node.data.id ?? ''), normalizedPackage),
|
|
82
|
+
},
|
|
83
|
+
})),
|
|
84
|
+
edges: elements.edges,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*** Resolves a readable label for the active package itself and for its descendants. */
|
|
89
|
+
function getRelativeNodeLabel(id: string, currentPackage: string): string {
|
|
90
|
+
if (!currentPackage) return id;
|
|
91
|
+
if (id === currentPackage) return id.split('.').at(-1) ?? id;
|
|
92
|
+
const descendantPrefix = currentPackage + '.';
|
|
93
|
+
return id.startsWith(descendantPrefix) ? id.slice(descendantPrefix.length) : id;
|
|
94
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NodeDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
/*** Reads a Cytoscape node-definition id through a typed graph-view boundary. */
|
|
4
|
+
export function readNodeDefinitionId(node: NodeDefinition): string | null {
|
|
5
|
+
const id: unknown = node.data.id;
|
|
6
|
+
return typeof id === 'string' ? id : null;
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ElementsDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
import { isIntrinsicGraphNode } from '@/utils/filter/isIntrinsicGraphNode';
|
|
4
|
+
|
|
5
|
+
/***
|
|
6
|
+
* Omits relationship-free containers in flat views, but preserves actual isolated leaf packages.
|
|
7
|
+
* @performance Index connected endpoints and ancestors once instead of scanning the graph per node.
|
|
8
|
+
*/
|
|
9
|
+
export function removeEmptyStructuralNodes(elements: ElementsDefinition): ElementsDefinition {
|
|
10
|
+
const connected = new Set(elements.edges.flatMap(edge => [edge.data.source, edge.data.target]));
|
|
11
|
+
const ancestors = new Set<string>();
|
|
12
|
+
for (const node of elements.nodes.filter(isIntrinsicGraphNode)) {
|
|
13
|
+
const segments = String(node.data.id ?? '').split('.');
|
|
14
|
+
for (let length = 1; length < segments.length; length += 1) {
|
|
15
|
+
ancestors.add(segments.slice(0, length).join('.'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
nodes: elements.nodes.filter(
|
|
20
|
+
node =>
|
|
21
|
+
!isIntrinsicGraphNode(node) ||
|
|
22
|
+
!ancestors.has(String(node.data.id)) ||
|
|
23
|
+
connected.has(String(node.data.id))
|
|
24
|
+
),
|
|
25
|
+
edges: elements.edges,
|
|
26
|
+
};
|
|
27
|
+
}
|
package/src/features/project-analysis/adapters/outbound/filesystem/readProjectSnapshotAsync.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { buildGraph } from '@/app/utils/buildGraph';
|
|
2
|
+
import { getParsedFileStructure } from '@/app/utils/getParsedFileStructure';
|
|
3
|
+
import { inspectParserLanguageAsync } from '@/app/utils/inspectParserLanguageAsync';
|
|
4
|
+
import type { ProjectSnapshot } from '@/types/projectAnalysis';
|
|
5
|
+
|
|
6
|
+
/*** Reads one project's language and source files and constructs its shared weighted graph. */
|
|
7
|
+
export async function readProjectSnapshotAsync(projectPath: string): Promise<ProjectSnapshot> {
|
|
8
|
+
const timeStart = Date.now();
|
|
9
|
+
const language = await inspectParserLanguageAsync(projectPath);
|
|
10
|
+
const files = await getParsedFileStructure(language.language, projectPath);
|
|
11
|
+
return { files, graph: buildGraph(files), language, projectPath, timeStart };
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ProjectSnapshot } from '@/types/projectAnalysis';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Shares only in-flight analysis of the same normalized project path within one server process.
|
|
5
|
+
* @performance
|
|
6
|
+
* Graph, tree and audit must share the parsed files and graph, not independently scan the project.
|
|
7
|
+
* Entries are removed after success or failure so later loads observe source changes and can retry.
|
|
8
|
+
*/
|
|
9
|
+
export function createProjectSnapshotReader(source: ProjectSnapshotSource) {
|
|
10
|
+
const pending = new Map<string, Promise<ProjectSnapshot>>();
|
|
11
|
+
|
|
12
|
+
/*** Joins the current project analysis or starts a fresh snapshot through the source port. */
|
|
13
|
+
function readAsync(projectPath: string): Promise<ProjectSnapshot> {
|
|
14
|
+
const current = pending.get(projectPath);
|
|
15
|
+
if (current !== undefined) return current;
|
|
16
|
+
|
|
17
|
+
const next = Promise.resolve()
|
|
18
|
+
.then(() => source.readAsync(projectPath))
|
|
19
|
+
.finally(() => pending.delete(projectPath));
|
|
20
|
+
pending.set(projectPath, next);
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { readAsync };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ProjectSnapshotSource {
|
|
28
|
+
readonly readAsync: (projectPath: string) => Promise<ProjectSnapshot>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createAuditFromSnapshot } from '@/features/audit/application/use-cases/createAuditFromSnapshot';
|
|
2
|
+
import { loadProjectSnapshotAsync } from '@/features/project-analysis/composition/loadProjectSnapshotAsync';
|
|
3
|
+
import { buildProjectTree } from '@/features/project-tree/application/use-cases/buildProjectTree';
|
|
4
|
+
import type { ProjectOverview } from '@/types/projectAnalysis';
|
|
5
|
+
|
|
6
|
+
/*** Derives graph, tree and audit evaluation from one freshly shared project snapshot. */
|
|
7
|
+
export async function loadProjectOverviewAsync(projectPath: string): Promise<ProjectOverview> {
|
|
8
|
+
const snapshot = await loadProjectSnapshotAsync(projectPath);
|
|
9
|
+
return {
|
|
10
|
+
graph: snapshot.graph,
|
|
11
|
+
tree: buildProjectTree(snapshot.files),
|
|
12
|
+
evaluation: createAuditFromSnapshot(snapshot).evaluation,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { readProjectSnapshotAsync } from '@/features/project-analysis/adapters/outbound/filesystem/readProjectSnapshotAsync';
|
|
4
|
+
import { createProjectSnapshotReader } from '@/features/project-analysis/application/use-cases/createProjectSnapshotReader';
|
|
5
|
+
import type { ProjectSnapshot } from '@/types/projectAnalysis';
|
|
6
|
+
|
|
7
|
+
/*** Wires the filesystem reader to process-local in-flight sharing using normalized project paths. */
|
|
8
|
+
export function loadProjectSnapshotAsync(projectPath: string): Promise<ProjectSnapshot> {
|
|
9
|
+
return reader.readAsync(resolve(projectPath));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const reader = createProjectSnapshotReader({ readAsync: readProjectSnapshotAsync });
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { type TreeItemNode, TreeView } from '@zora/tree-view';
|
|
3
|
+
import { FileCode2Icon, FolderIcon } from 'lucide-react';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import { useProjectTreeExpansion } from '@/features/project-tree/adapters/inbound/react/useProjectTreeExpansion';
|
|
7
|
+
import { findProjectTreeNode } from '@/features/project-tree/utils/findProjectTreeNode';
|
|
8
|
+
import type { ProjectTreeNode } from '@/types/projectTree';
|
|
9
|
+
|
|
10
|
+
/*** Adapts PKGViz's serializable project tree to the generated ZORA browser TreeView. */
|
|
11
|
+
export function ProjectTreePanel({ nodes, onSelect, selectedId }: ProjectTreePanelProps) {
|
|
12
|
+
const treeNodes = React.useMemo(() => nodes.map(toTreeItemNode), [nodes]);
|
|
13
|
+
const expansion = useProjectTreeExpansion(nodes, selectedId);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="px-2 pt-2 text-xs">
|
|
17
|
+
<TreeView
|
|
18
|
+
ariaLabel="Project tree"
|
|
19
|
+
expansionIndicator="folder"
|
|
20
|
+
expandedIds={expansion.expandedIds}
|
|
21
|
+
nodes={treeNodes}
|
|
22
|
+
selectedId={selectedId ?? undefined}
|
|
23
|
+
onExpandedChange={expansion.onExpandedChange}
|
|
24
|
+
onSelect={id => {
|
|
25
|
+
const node = findProjectTreeNode(nodes, id);
|
|
26
|
+
if (node) onSelect(node);
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*** Maps a portable project-tree node into the ZORA TreeView presentation contract. */
|
|
34
|
+
function toTreeItemNode(node: ProjectTreeNode): TreeItemNode {
|
|
35
|
+
return {
|
|
36
|
+
id: node.id,
|
|
37
|
+
label: node.label,
|
|
38
|
+
icon:
|
|
39
|
+
node.kind === 'directory' ? (
|
|
40
|
+
<FolderIcon aria-hidden size={14} />
|
|
41
|
+
) : (
|
|
42
|
+
<FileCode2Icon aria-hidden size={14} />
|
|
43
|
+
),
|
|
44
|
+
...(node.children ? { children: node.children.map(toTreeItemNode) } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ProjectTreePanelProps {
|
|
49
|
+
readonly nodes: readonly ProjectTreeNode[];
|
|
50
|
+
readonly onSelect: (node: ProjectTreeNode) => void;
|
|
51
|
+
readonly selectedId: string | null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { findProjectTreeNode } from '@/features/project-tree/utils/findProjectTreeNode';
|
|
4
|
+
import { getProjectTreeAncestorIds } from '@/features/project-tree/utils/getProjectTreeAncestorIds';
|
|
5
|
+
import type { ProjectTreeNode } from '@/types/projectTree';
|
|
6
|
+
|
|
7
|
+
/*** Opens a navigated folder once while preserving subsequent manual collapse and expansion. */
|
|
8
|
+
export function useProjectTreeExpansion(
|
|
9
|
+
nodes: readonly ProjectTreeNode[],
|
|
10
|
+
selectedId: string | null
|
|
11
|
+
) {
|
|
12
|
+
const [state, setState] = useState(() => ({
|
|
13
|
+
nodes,
|
|
14
|
+
selectedId,
|
|
15
|
+
ids: revealSelection(
|
|
16
|
+
nodes,
|
|
17
|
+
selectedId,
|
|
18
|
+
nodes.filter(node => node.kind === 'directory').map(node => node.id)
|
|
19
|
+
),
|
|
20
|
+
}));
|
|
21
|
+
if (state.nodes !== nodes || state.selectedId !== selectedId) {
|
|
22
|
+
setState({ nodes, selectedId, ids: revealSelection(nodes, selectedId, state.ids) });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
expandedIds: state.ids,
|
|
27
|
+
onExpandedChange: (ids: readonly string[]) => setState({ nodes, selectedId, ids }),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*** Adds ancestors and the selected directory without turning files into expandable items. */
|
|
32
|
+
function revealSelection(
|
|
33
|
+
nodes: readonly ProjectTreeNode[],
|
|
34
|
+
selectedId: string | null,
|
|
35
|
+
ids: readonly string[]
|
|
36
|
+
) {
|
|
37
|
+
if (selectedId === null) return ids;
|
|
38
|
+
const selected = findProjectTreeNode(nodes, selectedId);
|
|
39
|
+
return [
|
|
40
|
+
...new Set([
|
|
41
|
+
...ids,
|
|
42
|
+
...getProjectTreeAncestorIds(nodes, selectedId),
|
|
43
|
+
...(selected?.kind === 'directory' ? [selectedId] : []),
|
|
44
|
+
]),
|
|
45
|
+
];
|
|
46
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ParsedDirectory, ParsedFile } from '@/shared/types';
|
|
2
|
+
import type { ProjectTreeNode } from '@/types/projectTree';
|
|
3
|
+
|
|
4
|
+
/*** Converts parsed-project data into the serializable hierarchy consumed by the sidebar. */
|
|
5
|
+
export function buildProjectTree(
|
|
6
|
+
directory: ParsedDirectory,
|
|
7
|
+
parentPath = ''
|
|
8
|
+
): readonly ProjectTreeNode[] {
|
|
9
|
+
return Object.entries(directory)
|
|
10
|
+
.sort(compareEntries)
|
|
11
|
+
.map(([name, value]) => {
|
|
12
|
+
const itemPath = parentPath ? `${parentPath}/${name}` : name;
|
|
13
|
+
if (isParsedFile(value)) {
|
|
14
|
+
return {
|
|
15
|
+
graphPackage: value.package,
|
|
16
|
+
id: `file:${itemPath}`,
|
|
17
|
+
kind: 'file' as const,
|
|
18
|
+
label: name,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const children = buildProjectTree(value, itemPath);
|
|
23
|
+
return {
|
|
24
|
+
children,
|
|
25
|
+
graphPackage: getCommonGraphPackage(children),
|
|
26
|
+
id: `directory:${itemPath}`,
|
|
27
|
+
kind: 'directory' as const,
|
|
28
|
+
label: name,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*** Sorts folders before files while keeping each group alphabetic. */
|
|
34
|
+
function compareEntries(
|
|
35
|
+
[leftName, left]: [string, ParsedDirectory | ParsedFile],
|
|
36
|
+
[rightName, right]: [string, ParsedDirectory | ParsedFile]
|
|
37
|
+
) {
|
|
38
|
+
const kindDelta = Number(isParsedFile(left)) - Number(isParsedFile(right));
|
|
39
|
+
return kindDelta || leftName.localeCompare(rightName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*** Derives the graph package represented by a filesystem directory from its descendants. */
|
|
43
|
+
function getCommonGraphPackage(children: readonly ProjectTreeNode[]): string {
|
|
44
|
+
const packages = children
|
|
45
|
+
.map(child => child.graphPackage)
|
|
46
|
+
.filter(packageName => packageName.length);
|
|
47
|
+
const [firstPackage, ...remainingPackages] = packages;
|
|
48
|
+
if (!firstPackage) return '';
|
|
49
|
+
|
|
50
|
+
const firstSegments = firstPackage.split('.');
|
|
51
|
+
const mismatchIndex = firstSegments.findIndex((segment, index) =>
|
|
52
|
+
remainingPackages.some(packageName => packageName.split('.').at(index) !== segment)
|
|
53
|
+
);
|
|
54
|
+
const commonLength = mismatchIndex === -1 ? firstSegments.length : mismatchIndex;
|
|
55
|
+
return firstSegments.slice(0, commonLength).join('.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/*** Distinguishes parsed files from recursive directory records without relying on class names. */
|
|
59
|
+
function isParsedFile(value: ParsedDirectory | ParsedFile): value is ParsedFile {
|
|
60
|
+
return 'path' in value && typeof value.path === 'string' && 'package' in value;
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProjectTreeNode } from '@/types/projectTree';
|
|
2
|
+
|
|
3
|
+
/*** Resolves tree selection using the same package-descendant rule as graph double-press navigation. */
|
|
4
|
+
export function resolveProjectTreeNavigation(
|
|
5
|
+
node: ProjectTreeNode,
|
|
6
|
+
packageIds: readonly string[],
|
|
7
|
+
currentPackage: string
|
|
8
|
+
): string {
|
|
9
|
+
const packageId = node.graphPackage.replaceAll('/', '.').replace(/^\.+|\.+$/g, '');
|
|
10
|
+
if (!packageId) return currentPackage;
|
|
11
|
+
const descendantPrefix = packageId + '.';
|
|
12
|
+
return packageIds.some(id => id.startsWith(descendantPrefix)) ? packageId : currentPackage;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ProjectTreeNode } from '@/types/projectTree';
|
|
2
|
+
|
|
3
|
+
/*** Finds one project-tree node recursively by its stable generated id. */
|
|
4
|
+
export function findProjectTreeNode(
|
|
5
|
+
nodes: readonly ProjectTreeNode[],
|
|
6
|
+
id: string
|
|
7
|
+
): ProjectTreeNode | null {
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
if (node.id === id) return node;
|
|
10
|
+
const nested = node.children ? findProjectTreeNode(node.children, id) : null;
|
|
11
|
+
if (nested) return nested;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|