🌳
pt0/deployF/testsF/findDeadCodeAI.mts
1import * as _ from 'lodash-es'
2import { execSync } from 'child_process'
3import { existsSync } from 'fs'
4import path from 'path'
24import { type absFileDirPath, tsAbsPath } from '../../ptDirF.mts'
28const epsToAllPathsTagged = (tag: string, epPtPathsA: string[]) =>
29 betDurMs(`epsToAllPaths:${tag}:${epPtPathsA.length}seeds`, () => epsToAllPaths({epPtPathsA}))
31const migratedDirPrefixA = ['pt0/', '.opencode/']
33const getSubmodulePrefixA = (): string[] => {
34 const gitmodulesPath = `${ptDir}/.gitmodules`
35 if (!existsSync(gitmodulesPath)) return []
36 const out = execSync(`git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$'`, {cwd: ptDir, encoding: 'utf8'})
37 return out.trim().split('\n').filter(Boolean).map(line => line.split(' ')[1] + '/')
40const submodulePrefixA = getSubmodulePrefixA()
41const excludedDirPrefixA = [`${tmpDirName}/`, ...submodulePrefixA]
42const buildArtifactDirInfixA = ['/.next/', '/.next-test/']
43const isExcludedPath = (p: string) => excludedDirPrefixA.some(prefix => p.startsWith(prefix)) || buildArtifactDirInfixA.some(infix => p.includes(infix))
44const isSubmodulePath = (p: string) => submodulePrefixA.some(prefix => p.startsWith(prefix))
46const deadExportExcludePrefixes = ['.opencode/']
47const isDeadExportExcluded = (p: string) => deadExportExcludePrefixes.some(prefix => p.startsWith(prefix)) || p.includes('/public/')
48const nextRouteExports = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'runtime', 'dynamic', 'revalidate'])
49const knownDynamicExports = new Set<string>()
51export const getPtSubdirFromEp = async (epPath: string) => {
52 const appPath = await getMonorSubdirForRegEp(epPath)
53 if (!appPath) return null
54 const nextConfigPath = pathDownJoin(ptDir, appPath, 'next.config.mjs')
55 if (!await fileExists(nextConfigPath)) return null
56 return appPath
59export const getPageEpsFromNextEps = async (epA: string[]) => {
60 const ptSubdirA = _.compact(await Promise.all(epA.map(getPtSubdirFromEp)))
61 const uniquePtSubdirs = _.uniq(ptSubdirA)
62 const pageEpA: string[] = []
63 for (const appPath of uniquePtSubdirs) {
64 appCfgCtx.enterWith({ appPath })
65 const eps = await nextAppPaths()
66 pageEpA.push(...eps)
67 }
68 return _.uniq(pageEpA)
71const getVirtualImportsForEps = async (eps: string[]) => _.flatten(await Promise.all(eps.map(getVirtualImportsForDeployScript)))
73const getAnchorIncludedImportPaths = async () => {
74 const anchorFilesA = await getAllAnchoredPaths()
75 const existingA = _.compact(await Promise.all(anchorFilesA.map(async (p: string) => await fileExists(pathDownJoin(ptDir, p)) ? p : null)))
76 return existingA.length > 0 ? await epsToAllPathsTagged('anchor', existingA) : []
79const getPathBinInfo = async () => {
80 const binDir = pathDownJoin(ptDir, 'pt0/path_bin')
81 try {
82 const entries = (await lsFilePathsRec(binDir)).filter((p: string) => !path.extname(p))
83 const jsBinScripts: string[] = [], importTargets: string[] = []
84 for (const absP of entries) {
85 const contents = await read1File(absP)
86 if (!/^#!.*\b(node|ptnode)\b/.test(contents.split('\n')[0])) continue
87 const ptPath = absPathToPtPath(absP)
88 jsBinScripts.push(ptPath)
89 const importsH = await acornSingleFile(ptPath as unknown as absFileDirPath)
90 importTargets.push(...Object.keys(importsH))
91 }
92 const transitiveImportPaths = importTargets.length > 0 ? await epsToAllPathsTagged('pathBin', importTargets) : []
93 return { jsBinScripts, transitiveImportPaths }
94 } catch { return { jsBinScripts: [] as string[], transitiveImportPaths: [] as string[] } }
97export const getDynImportPaths = async (epPathsA: string[]) => {
98 const ptDirJoinRe = /path\.join\(ptDir,\s*['"]([^'"]+)['"]\)/g
99 const dynPaths: string[] = []
100 for (const p of epPathsA) {
101 if (!isPtCodeFileExt(p)) continue
102 try {
103 const contents = await read1File(pathDownJoin(ptDir, p))
104 for (const m of contents.matchAll(ptDirJoinRe)) {
105 if (isPtCodeFileExt(m[1]) && await fileExists(pathDownJoin(ptDir, m[1]))) dynPaths.push(m[1])
106 }
107 } catch {}
108 }
109 return dynPaths.length > 0 ? await epsToAllPathsTagged('dynImports', dynPaths) : []
112const deployDeclaredPropA = [{name: 'inclPtPathA', isArray: true}, {name: 'bundleEntryPtPath', isArray: false}] as const
114const getDeployDeclaredPaths = async (epPathsA: string[]) => {
115 const paths: string[] = []
116 for (const p of epPathsA) {
117 if (!isPtCodeFileExt(p)) continue
118 try {
119 const contents = await read1File(pathDownJoin(ptDir, p))
120 if (!deployDeclaredPropA.some(prop => contents.includes(prop.name))) continue
121 const ast = acornParse({contents, ptPath: p as unknown as absFileDirPath})
122 walkAcornNodes(ast, (node: any) => {
123 if (node.type !== 'Property') return
124 const prop = deployDeclaredPropA.find(prop => node.key?.type === 'Identifier' && node.key.name === prop.name)
125 if (!prop) return
126 if (prop.isArray) {
127 if (node.value?.type !== 'ArrayExpression') return
128 for (const elem of node.value.elements) {
129 if (elem?.type === 'Literal' && typeof elem.value === 'string') paths.push(elem.value)
130 }
131 } else if (node.value?.type === 'Literal' && typeof node.value.value === 'string') {
132 paths.push(node.value.value)
133 }
134 })
135 } catch {}
136 }
137 return paths.length > 0 ? await epsToAllPathsTagged('deployDeclared', paths) : []
140export const buildEpPathSets = async ({onlyEpA}: {onlyEpA: string[]}) => {
141 return await betDurMs('buildEpPathSets:total', async () => {
142 // Virtual imports folded into the ep seeds so their transitive closure is computed by the single
143 // pt0Eps/allEps pass (closure(A∪B)=closure(A)∪closure(B)), avoiding two dedicated madge passes.
144 const pt0VirtualPaths = await getVirtualImportsForEps(onlyEpA)
145 const pageEpA = await getPageEpsFromNextEps(onlyEpA)
146 const combinedEpA = _.uniq([...onlyEpA, ...pageEpA, ...pt0VirtualPaths])
147 let epPaths = await epsToAllPathsTagged('pt0Eps', combinedEpA)
149 const appPathA = _.compact(await Promise.all(onlyEpA.map(getPtSubdirFromEp)))
150 const pkgJsonPathsA = (await Promise.all(_.uniq(appPathA).map(appPath => getPkgJsonPtPathsA({appPath})))).flat() as unknown as string[]
151 epPaths = _.uniq([...epPaths, ...pkgJsonPathsA]).filter(p => !isExcludedPath(p))
153 const anchorImportPaths = await getAnchorIncludedImportPaths()
154 const { jsBinScripts: pathBinScriptsA, transitiveImportPaths: pathBinPaths } = await getPathBinInfo()
155 const pt0DynImportPaths = await getDynImportPaths(epPaths)
156 const ossReplaceTargets = await getOssReplaceTargets()
157 const pt0DeployDeclaredPaths = await getDeployDeclaredPaths(onlyEpA)
158 const pt0EpPathsSet = new Set([...onlyEpA, ...epPaths, ...pt0VirtualPaths, ...anchorImportPaths, ...pt0DynImportPaths, ...pt0DeployDeclaredPaths, ...pathBinPaths, ...ossReplaceTargets])
160 const allRegisteredEpPaths = _.map(getRegisteredEpsRegistry(), 'regEpPath')
161 const allVirtualPaths = await getVirtualImportsForEps(allRegisteredEpPaths)
162 const allEpPagePaths = await getPageEpsFromNextEps(allRegisteredEpPaths)
163 const allCombinedEpA = _.uniq([...allRegisteredEpPaths, ...allEpPagePaths, ...allVirtualPaths])
164 const allEpPaths = await epsToAllPathsTagged('allEps', allCombinedEpA)
165 const dynImportPaths = await getDynImportPaths(allEpPaths)
166 const deployDeclaredPaths = await getDeployDeclaredPaths(allRegisteredEpPaths)
167 const allEpPathsSet = new Set([...allCombinedEpA, ...allEpPaths, ...allVirtualPaths, ...anchorImportPaths, ...dynImportPaths, ...deployDeclaredPaths, ...pathBinPaths, ...ossReplaceTargets])
169 return { epPaths, pt0EpPathsSet, allEpPathsSet, pathBinScriptsA }
170 })
173export const findDeadCode = async ({showAll}: {showAll?: boolean} = {}) => {
174 const pt0EpA = getAllRegisteredEps().filter((p: string) => migratedDirPrefixA.some(prefix => p.startsWith(prefix)))
175 const { pt0EpPathsSet, allEpPathsSet, pathBinScriptsA } = await buildEpPathSets({onlyEpA: pt0EpA})
177 // Dead files: migrated code files not in any EP import tree
178 const allMigratedDirFilesA = _.flatten(await Promise.all(
179 migratedDirPrefixA.map(async prefix => {
180 const dirPath = pathDownJoin(ptDir, prefix.replace(/\/$/, ''))
181 const absFiles = await lsFilePathsRec(dirPath)
182 return absFiles.map(absPathToPtPath)
183 })
184 ))
185 const migratedCodeFilesA = allMigratedDirFilesA
186 .filter(isPtCodeFileExt)
187 .filter((p: string) => !p.includes('/migrationsF/') && !p.endsWith('.d.ts') && !isExcludedPath(p) && !p.includes('/public/'))
189 const deadFilesA = migratedCodeFilesA.filter((p: string) => !allEpPathsSet.has(p))
190 const nonPt0OnlyFilesA = migratedCodeFilesA.filter((p: string) => !pt0EpPathsSet.has(p) && allEpPathsSet.has(p))
192 // Dead exports: unused exports across all EP code paths
193 const allEpCodePaths = [...allEpPathsSet].filter(isPtCodeFileExt).filter((p: string) => !isSubmodulePath(p))
194 const ptmvpinnedPaths = allMigratedDirFilesA.filter((p: string) => p.includes(ptmvpinnedInfix))
195 const reportPaths = allEpCodePaths.filter((p: string) => !isDeadExportExcluded(p))
196 const extraImportScanPaths = [...allEpCodePaths.filter(isDeadExportExcluded), ...pathBinScriptsA, ...ptmvpinnedPaths]
197 const allDeadExportsByFile = await getUnusedExportsByFile({
198 pathsA: reportPaths.map(p => pathDownJoin(ptDir, p) as unknown as absFileDirPath),
199 extraImportScanPathsA: extraImportScanPaths.map(p => pathDownJoin(ptDir, p) as unknown as absFileDirPath),
200 })
202 const deadExportsByFile = Object.fromEntries(
203 Object.entries(allDeadExportsByFile)
204 .filter(([p]) => showAll || p.startsWith('pt0/'))
205 .map(([p, exports]) => {
206 let filtered = exports.filter(name => !knownDynamicExports.has(name) && !name.endsWith('K8sRes'))
207 if (isNextRouteHandlerFile(p)) filtered = filtered.filter(name => !nextRouteExports.has(name))
208 return [p, filtered]
209 })
210 .filter(([, exports]) => (exports as string[]).length > 0)
211 )
213 const {reExportBloatA} = await findReExportBloat()
215 return { deadFilesA, nonPt0OnlyFilesA, deadExportsByFile, reExportBloatA }