(root: string)
| 1128 | } |
| 1129 | |
| 1130 | async function getPythonDeps(root: string): Promise<string[]> { |
| 1131 | const deps: string[] = []; |
| 1132 | // Check root and common subdirectories |
| 1133 | const searchDirs = [root]; |
| 1134 | try { |
| 1135 | const entries = await readdir(root, { withFileTypes: true }); |
| 1136 | for (const entry of entries) { |
| 1137 | if (entry.isDirectory() && !entry.name.startsWith(".") && !IGNORE_DIRS.has(entry.name)) { |
| 1138 | searchDirs.push(join(root, entry.name)); |
| 1139 | } |
| 1140 | } |
| 1141 | } catch {} |
| 1142 | for (const dir of searchDirs) { |
| 1143 | try { |
| 1144 | const req = await readFile(join(dir, "requirements.txt"), "utf-8"); |
| 1145 | await parsePythonRequirements(req, dir, deps); |
| 1146 | } catch {} |
| 1147 | // Pipfile support (poetry-style, older Flask/Python projects) |
| 1148 | try { |
| 1149 | const pipfile = await readFile(join(dir, "Pipfile"), "utf-8"); |
| 1150 | let inPackages = false; |
| 1151 | for (const line of pipfile.split("\n")) { |
| 1152 | const trimmed = line.trim(); |
| 1153 | if (trimmed === "[packages]" || trimmed === "[dev-packages]") { |
| 1154 | inPackages = trimmed === "[packages]"; |
| 1155 | continue; |
| 1156 | } |
| 1157 | if (trimmed.startsWith("[")) { inPackages = false; continue; } |
| 1158 | if (inPackages && trimmed.includes("=")) { |
| 1159 | const name = trimmed.split("=")[0].trim().toLowerCase().replace(/_/g, "-"); |
| 1160 | if (name && !name.startsWith("#") && !deps.includes(name)) deps.push(name); |
| 1161 | } |
| 1162 | } |
| 1163 | } catch {} |
| 1164 | try { |
| 1165 | const toml = await readFile(join(dir, "pyproject.toml"), "utf-8"); |
| 1166 | parsePyprojectProjectDeps(toml, deps); |
| 1167 | parsePyprojectPoetryDeps(toml, deps); |
| 1168 | } catch {} |
| 1169 | } |
| 1170 | return deps; |
| 1171 | } |
| 1172 | |
| 1173 | /** PEP 621: [project] dependencies = [...] */ |
| 1174 | function parsePyprojectProjectDeps(toml: string, deps: string[]): void { |
no test coverage detected