diff --git a/src/repodock/detect.py b/src/repodock/detect.py index b5b9859..53d1365 100644 --- a/src/repodock/detect.py +++ b/src/repodock/detect.py @@ -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(): @@ -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 @@ -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 [] diff --git a/tests/test_detect.py b/tests/test_detect.py index 5d4f9a9..7b772e0 100644 --- a/tests/test_detect.py +++ b/tests/test_detect.py @@ -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")