mirror of
https://github.com/Jeuners/ECC.git
synced 2026-09-09 15:02:30 +02:00
fix(project-detect): parse Python deps pinned with ~ and @ (was losing framework detection on compatible-release pins) (#2498)
* fix(project-detect): parse Python deps pinned with ~= and @ direct references * test(project-detect): cover ~= compatible-release and @ direct-reference parsing * test(project-detect): cover direct references * fix(project-detect): skip bare VCS/URL requirement lines in getPythonDeps A requirements.txt line like git+https://host/repo.git#egg=pkg carries no leading package name, so the delimiter split recorded the whole URL fragment as a dependency. Skip names that start with git+ or contain a URL scheme, and tighten the test assertions so any leaked URL, scheme, or @ delimiter fails loudly.
This commit is contained in:
parent
5d68ef3617
commit
5da21c2b66
2 changed files with 58 additions and 3 deletions
|
|
@ -198,10 +198,13 @@ function getPythonDeps(projectDir) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('-')) {
|
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('-')) {
|
||||||
const name = trimmed
|
const name = trimmed
|
||||||
.split(/[>=<![;]/)[0]
|
.split(/[\s>=<!~@[;]/)[0]
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
if (name) deps.push(name);
|
// Bare VCS/URL requirement lines (e.g. `git+https://...#egg=pkg`)
|
||||||
|
// carry no leading package name; skip them instead of recording
|
||||||
|
// the URL fragment as a dependency name.
|
||||||
|
if (name && !name.startsWith('git+') && !name.includes('://')) deps.push(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +223,7 @@ function getPythonDeps(projectDir) {
|
||||||
block.match(/"([^"]+)"/g)?.forEach(m => {
|
block.match(/"([^"]+)"/g)?.forEach(m => {
|
||||||
const name = m
|
const name = m
|
||||||
.replace(/"/g, '')
|
.replace(/"/g, '')
|
||||||
.split(/[>=<![;]/)[0]
|
.split(/[\s>=<!~@[;]/)[0]
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
if (name) deps.push(name);
|
if (name) deps.push(name);
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,29 @@ function runTests() {
|
||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('detects flask/fastapi from ~= compatible-release pins (requirements.txt)', () => {
|
||||||
|
const dir = createTempDir();
|
||||||
|
try {
|
||||||
|
writeTestFile(dir, 'requirements.txt', 'fastapi~=0.110\nflask~=3.0\nuvicorn');
|
||||||
|
const result = detectProjectType(dir);
|
||||||
|
assert.ok(result.frameworks.includes('fastapi'), `fastapi not detected: ${JSON.stringify(result.frameworks)}`);
|
||||||
|
assert.ok(result.frameworks.includes('flask'), `flask not detected: ${JSON.stringify(result.frameworks)}`);
|
||||||
|
} finally {
|
||||||
|
cleanupDir(dir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('detects fastapi from ~= pin in pyproject.toml dependencies', () => {
|
||||||
|
const dir = createTempDir();
|
||||||
|
try {
|
||||||
|
writeTestFile(dir, 'pyproject.toml', '[project]\nname = "test"\ndependencies = [\n "fastapi~=0.110",\n "uvicorn"\n]');
|
||||||
|
const result = detectProjectType(dir);
|
||||||
|
assert.ok(result.frameworks.includes('fastapi'), `fastapi not detected: ${JSON.stringify(result.frameworks)}`);
|
||||||
|
} finally {
|
||||||
|
cleanupDir(dir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// TypeScript/JavaScript detection
|
// TypeScript/JavaScript detection
|
||||||
console.log('\nTypeScript/JavaScript Detection:');
|
console.log('\nTypeScript/JavaScript Detection:');
|
||||||
|
|
||||||
|
|
@ -388,6 +411,35 @@ function runTests() {
|
||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('getPythonDeps strips ~= compatible-release pins', () => {
|
||||||
|
const dir = createTempDir();
|
||||||
|
try {
|
||||||
|
writeTestFile(dir, 'requirements.txt', 'fastapi~=0.110\nflask~=3.0');
|
||||||
|
const deps = getPythonDeps(dir);
|
||||||
|
assert.ok(deps.includes('fastapi'), `fastapi missing from deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(deps.includes('flask'), `flask missing from deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(!deps.includes('flask~'));
|
||||||
|
} finally {
|
||||||
|
cleanupDir(dir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('getPythonDeps strips @ direct-reference URLs and git+ VCS forms', () => {
|
||||||
|
const dir = createTempDir();
|
||||||
|
try {
|
||||||
|
writeTestFile(dir, 'requirements.txt', 'pkg @ git+https://github.com/user/repo.git\nother @ https://example.com/pkg.tar.gz\ngit-dep @ git+https://github.com/user/dep.git@v2.0\ngit+https://github.com/user/repo.git#egg=pkg');
|
||||||
|
const deps = getPythonDeps(dir);
|
||||||
|
assert.ok(deps.includes('pkg'), `pkg missing from deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(deps.includes('other'), `other missing from deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(deps.includes('git-dep'), `git-dep missing from deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(!deps.some(d => d.includes('git+')), `VCS URL leaked into deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(!deps.some(d => d.includes('://')), `URL leaked into deps: ${JSON.stringify(deps)}`);
|
||||||
|
assert.ok(!deps.some(d => d.includes('@')), `@ delimiter leaked into deps: ${JSON.stringify(deps)}`);
|
||||||
|
} finally {
|
||||||
|
cleanupDir(dir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('getGoDeps reads go.mod require block', () => {
|
if (test('getGoDeps reads go.mod require block', () => {
|
||||||
const dir = createTempDir();
|
const dir = createTempDir();
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue