🌳
pt0/deployF/jsImportsF/getHowImportsF.mts
2import * as _ from 'lodash-es'
3import { ptMadge } from './ptMadgeF.mts'
19type MadgeH = Record<string, string[]>
21// BFS path finder in madge graph
22const findPathInMadge = (madgeH: MadgeH, source: string, target: string): string[] | null => {
23 if (source === target) return [source]
24 const visited = new Set([source])
25 const queue: string[][] = [[source]]
26 while (queue.length > 0) {
27 const path = queue.shift()!
28 const current = path[path.length - 1]
29 const imports = madgeH[current] || []
30 for (const imp of imports) {
31 if (imp === target) return [...path, imp]
32 if (!visited.has(imp)) {
33 visited.add(imp)
34 queue.push([...path, imp])
35 }
36 }
37 }
38 return null
41// Unified howimports with deploy-include awareness (used by action and MCP tool)
42export const getHowImportsWithDeployIncludes = async ({epA, tgtPath, useReverseFormat = false}: {epA: string[], tgtPath: string, useReverseFormat?: boolean}) => {
43 // 1. Check direct deploy-includes (file is directly in pages/app/public)
44 const directDeployIncluders = await getDeployScriptsIncludingFile(tgtPath)
46 // 2. Get ES import paths from entrypoints
47 const {retH, transitiveImporters, madgeH} = await get1HowImportsFromEpA({epA, tgtPath})
49 const outputA = []
50 const shownEpSet = new Set()
52 // 3. Format ES import trees
53 if (retH && (retH instanceof Set ? retH.size > 0 : Object.keys(retH).length > 0)) {
54 const filteredEpA = _.filter(epA, p => isPtCodeFileExt(p) && transitiveImporters.has(p))
55 const epPathsA = buildImportTreePaths({madgeH, epA: filteredEpA, tgtPath, transitiveImporters})
56 if (epPathsA.length > 0) {
57 const formatter = useReverseFormat ? formatImportTreeReverse : formatImportTree
58 outputA.push(formatter({epPathsA, tgtPath}))
59 filteredEpA.forEach(ep => shownEpSet.add(ep))
60 }
61 }
63 // 4. Add direct deploy-include entries
64 for (const regEpPath of directDeployIncluders) {
65 if (shownEpSet.has(regEpPath)) continue
66 shownEpSet.add(regEpPath)
67 const appPath = await getMonorSubdirForRegEp(regEpPath)
68 outputA.push(`\n${regEpPath} (deploy-include)`)
69 outputA.push(` └─[deploys ${appPath}/]─> ${tgtPath}`)
70 }
72 // 5. Trace through virtual imports (transitive deploy-includes)
73 const epVirtualImportsH: Record<string, string[]> = {}
74 const allVirtualImportsSet = new Set<string>()
75 for (const ep of epA) {
76 if (shownEpSet.has(ep)) continue
77 const appPath = await getMonorSubdirForRegEp(ep)
78 if (!appPath) continue
79 const virtualImportsA = await getVirtualImportsForDeployScript(ep)
80 if (!virtualImportsA.length) continue
81 const codeVirtualImportsA = _.filter(virtualImportsA, isPtCodeFileExt)
82 if (!codeVirtualImportsA.length) continue
83 epVirtualImportsH[ep] = codeVirtualImportsA
84 codeVirtualImportsA.forEach(vi => allVirtualImportsSet.add(vi))
85 }
87 // Build madge once for all virtual imports (batch optimization)
88 const allVirtualImportsA = [...allVirtualImportsSet] as string[]
89 let combinedMadgeH: MadgeH = {}
90 if (allVirtualImportsA.length) {
91 try {
92 combinedMadgeH = await ptMadge(allVirtualImportsA)
93 } catch { /* skip if madge fails */ }
94 }
96 // Find paths through virtual imports
97 for (const ep of _.keys(epVirtualImportsH)) {
98 const codeVirtualImportsA = epVirtualImportsH[ep]!
99 const appPath = await getMonorSubdirForRegEp(ep)
100 for (const pageFile of codeVirtualImportsA) {
101 const pathToTarget = findPathInMadge(combinedMadgeH, pageFile, tgtPath)
102 if (pathToTarget) {
103 shownEpSet.add(ep)
104 outputA.push(`\n${ep} (deploy-include)`)
105 outputA.push(` └─[deploys ${appPath}/]─> ${pathToTarget.join('\n └─> ')}`)
106 break
107 }
108 }
109 }
111 return outputA.join('\n') || 'No entrypoints import this file'
114export const getHowImports = async () => {
115 const action = getAction()
116 const argv = getProcArgv()
118 const lastNonFlagArg = _.findLast(argv, arg => !arg.startsWith('-'))
119 const tgtPath = lastNonFlagArg != action && lastNonFlagArg
120 if (!tgtPath || tgtPath.includes('..')) return
122 const epScope = cliArg(epScopeKey) || epScopes.deployjs
124 if (epScope === epScopes.deployjs) {
125 const {deployEp, importMetaUrl} = getAppCfg()
126 if (!deployEp && !importMetaUrl) {
127 const epA = getAllRegisteredEps()
128 const output = await getHowImportsWithDeployIncludes({epA, tgtPath, useReverseFormat: true})
129 console.log(output)
130 } else {
131 const {epA} = await getRakeEpsPaths({defaultEpScope: epScopes.deployjs})
132 await getHowImportsFromEpA({epA, tgtPath})
133 }
134 } else if (epScope === epScopes.dockerjs || epScope === epScopes.pubfejs) {
135 // Trace from current app's docker entrypoints (pages/app/public) with deploy-include awareness
136 const {epA} = await getRakeEpsPaths({defaultEpScope: epScope})
137 const output = await getHowImportsWithDeployIncludes({epA, tgtPath, useReverseFormat: true})
138 console.log(output)
139 } else {
140 // alljs: use all registered entrypoints
141 const epA = getAllRegisteredEps()
142 const output = await getHowImportsWithDeployIncludes({epA, tgtPath, useReverseFormat: true})
143 console.log(output)
144 }
147export const getHowImportsFromEpA = async ({epA, tgtPath}: {epA: string[], tgtPath: string}) => {
148 const {retH, transitiveImporters, madgeH} = await get1HowImportsFromEpA({epA, tgtPath})
149 if (retH && (retH instanceof Set ? retH.size > 0 : Object.keys(retH).length > 0)) {
150 const filteredEpA = _.filter(epA, ptPath =>
151 isPtCodeFileExt(ptPath) && transitiveImporters.has(ptPath)
152 )
153 const epPathsA = buildImportTreePaths({madgeH, epA: filteredEpA, tgtPath, transitiveImporters})
154 if (epPathsA.length > 0) {
155 const treeView = formatImportTreeReverse({epPathsA, tgtPath})
156 console.log(treeView)
157 } else {
158 console.log('No entrypoints import this file')
159 }
160 } else {
161 console.log('No entrypoints import this file')
162 }
165type HowImportsResult = {retH: Set<string> | MadgeH, transitiveImporters: Set<string>, madgeH: MadgeH}
167export const get1HowImportsFromEpA = async ({epA, tgtPath}: {epA: string[], tgtPath?: string}): Promise<HowImportsResult> => {
168 const calcDockerPaths = await epsToAllPaths({epPtPathsA: epA})
169 const doesInclTgtPath = _.includes(calcDockerPaths, tgtPath)
171 const madgeH = await ptMadge(epA)
173 let retH: Set<string> | MadgeH = madgeH
174 let transitiveImporters = new Set<string>()
176 if (tgtPath) {
177 // Get direct importers for display
178 retH = getDirectImports({madgeH, relPath: tgtPath}) as Set<string>
180 // Get all transitive importers using existing function
181 const relevantMadgeH = reduceMadgeRelH({madgeH, relPath: tgtPath})
182 transitiveImporters = new Set(_.keys(relevantMadgeH))
183 }
185 if (doesInclTgtPath || !tgtPath) {
186 return {retH, transitiveImporters, madgeH}
187 }
188 return {retH: new Set<string>(), transitiveImporters: new Set<string>(), madgeH}