Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/repodock/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ def _node(root: Path, win: bool) -> list[Candidate]:
pkg = json.loads(_read(pkg_path))
except ValueError:
pkg = {}
scripts = pkg.get("scripts") or {}
if not isinstance(pkg, dict):
return []
scripts = pkg.get("scripts")
if not isinstance(scripts, dict):
scripts = {}
if (root / "pnpm-lock.yaml").exists():
pm, install = "pnpm", "pnpm install"
elif (root / "yarn.lock").exists():
Expand All @@ -175,12 +179,12 @@ def _node(root: Path, win: bool) -> list[Candidate]:
common = dict(kind="node", tool=pm, deps=install if has_deps else None, deps_marker="node_modules" if has_deps else None)
out = []
for name in ("dev", "start", "serve", "preview", "develop"):
if name in scripts:
if isinstance(scripts.get(name), str) and scripts[name].strip():
cmd = f"{pm} start" if name == "start" and pm in ("npm", "yarn") else f"{pm} run {name}"
out.append(Candidate(f"{pm} {'start' if name == 'start' else 'run ' + name}", cmd, notes=[f"runs: {scripts[name]}"], **common))
if not out:
main = pkg.get("main")
for entry in ([main] if main else []) + ["index.js", "server.js", "app.js", "main.js"]:
for entry in ([main] if isinstance(main, str) and main else []) + ["index.js", "server.js", "app.js", "main.js"]:
if entry and (root / entry).is_file():
out.append(Candidate(f"node {entry}", f"node {q(entry)}", **{**common, "tool": "node"}))
break
Expand All @@ -195,10 +199,15 @@ def _deno(root: Path, win: bool) -> list[Candidate]:
if (root / name).is_file():
text = re.sub(r"^\s*//.*$", "", _read(root / name), flags=re.M)
try:
tasks = (json.loads(text).get("tasks") or {})
config = json.loads(text)
tasks = config.get("tasks") if isinstance(config, dict) else {}
if not isinstance(tasks, dict):
tasks = {}
except ValueError:
tasks = {}
return [Candidate(f"deno task {t}", f"deno task {t}", "deno", tool="deno") for t in ("dev", "start") if t in tasks]
# Deno supports both command strings and task objects (including dependency-only tasks).
return [Candidate(f"deno task {t}", f"deno task {t}", "deno", tool="deno") for t in ("dev", "start")
if isinstance(tasks.get(t), dict) or (isinstance(tasks.get(t), str) and tasks[t].strip())]
return []


Expand Down
21 changes: 21 additions & 0 deletions tests/test_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ def test_node_without_scripts(self):
c = self.first({"package.json": json.dumps({"main": "lib/app.js"}), "lib/app.js": ""})
self.assertEqual(c.command, "node lib/app.js")

def test_invalid_node_metadata_does_not_hide_other_project_types(self):
for value in (None, [], True, 42, "not a package"):
with self.subTest(value=value):
found = detect(self.repo({"package.json": json.dumps(value), "main.py": "print('hello')"}), "linux")
self.assertTrue(any(c.kind == "python" for c in found))
for scripts in ([], "dev", 42, {"dev": None}, {"dev": []}, {"dev": " "}):
with self.subTest(scripts=scripts):
found = detect(self.repo({"package.json": json.dumps({"scripts": scripts, "main": 42}), "index.js": ""}), "linux")
self.assertEqual([c.command for c in found], ["node index.js"])

def test_invalid_deno_metadata_does_not_hide_other_project_types(self):
for value in (None, [], True, {"tasks": []}, {"tasks": 42}, {"tasks": {"dev": None}}):
with self.subTest(value=value):
found = detect(self.repo({"deno.json": json.dumps(value), "main.py": "print('hello')"}), "linux")
self.assertTrue(any(c.kind == "python" for c in found))
self.assertFalse(any(c.kind == "deno" for c in found))
c = self.first({"deno.json": json.dumps({"tasks": {"dev": {"command": "deno run main.ts"}}})})
self.assertEqual(c.command, "deno task dev")
c = self.first({"deno.json": json.dumps({"tasks": {"dev": {"dependencies": ["client", "server"]}}})})
self.assertEqual(c.command, "deno task dev")

def test_python_entry_with_requirements(self):
c = self.first({"requirements.txt": "requests\n", "main.py": "print(1)"})
self.assertEqual(c.command, "{python} main.py")
Expand Down
Loading