mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-12 02:17:04 +00:00
fix js workspace import resolution (#1352)
Fixes #1334: detect npm/yarn package.json workspaces (array + yarn object form), pnpm precedence preserved. Reviewed: full suite 2087 passed, no regressions. Thanks @balloon72.
This commit is contained in:
+38
-3
@@ -99,6 +99,7 @@ def _file_node_id(rel_path: Path) -> str:
|
||||
|
||||
_TSCONFIG_ALIAS_CACHE: dict[str, dict[str, str]] = {}
|
||||
_WORKSPACE_PACKAGE_CACHE: dict[str, dict[str, Path]] = {}
|
||||
_WORKSPACE_MANIFEST_NAMES = ("pnpm-workspace.yaml", "package.json")
|
||||
_JS_CACHE_BYPASS_SUFFIXES = {".js", ".jsx", ".mjs", ".ts", ".tsx", ".vue", ".svelte"}
|
||||
_JS_RESOLVE_EXTS = (".ts", ".tsx", ".svelte", ".js", ".jsx", ".mjs")
|
||||
_JS_INDEX_FILES = ("index.ts", "index.tsx", "index.svelte", "index.js", "index.jsx", "index.mjs")
|
||||
@@ -299,10 +300,18 @@ def _find_workspace_root(start_dir: Path) -> Path | None:
|
||||
for candidate in [current, *current.parents]:
|
||||
if (candidate / "pnpm-workspace.yaml").exists():
|
||||
return candidate
|
||||
package_json = candidate / "package.json"
|
||||
if package_json.is_file():
|
||||
try:
|
||||
data = json.loads(package_json.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
continue
|
||||
if "workspaces" in data:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _workspace_globs(workspace_file: Path) -> list[str]:
|
||||
def _pnpm_workspace_globs(workspace_file: Path) -> list[str]:
|
||||
globs: list[str] = []
|
||||
in_packages = False
|
||||
for raw_line in workspace_file.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
@@ -322,16 +331,42 @@ def _workspace_globs(workspace_file: Path) -> list[str]:
|
||||
return globs
|
||||
|
||||
|
||||
def _workspace_globs(root: Path) -> list[str]:
|
||||
pnpm_workspace = root / "pnpm-workspace.yaml"
|
||||
if pnpm_workspace.exists():
|
||||
return _pnpm_workspace_globs(pnpm_workspace)
|
||||
|
||||
package_json = root / "package.json"
|
||||
try:
|
||||
data = json.loads(package_json.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
workspaces = data.get("workspaces")
|
||||
if isinstance(workspaces, list):
|
||||
return [item for item in workspaces if isinstance(item, str) and not item.startswith("!")]
|
||||
if isinstance(workspaces, dict):
|
||||
packages = workspaces.get("packages")
|
||||
if isinstance(packages, list):
|
||||
return [item for item in packages if isinstance(item, str) and not item.startswith("!")]
|
||||
return []
|
||||
|
||||
|
||||
def _load_workspace_packages(start_dir: Path) -> dict[str, Path]:
|
||||
root = _find_workspace_root(start_dir)
|
||||
if root is None:
|
||||
return {}
|
||||
key = str(root)
|
||||
manifest_mtimes = tuple(
|
||||
(name, (root / name).stat().st_mtime_ns)
|
||||
for name in _WORKSPACE_MANIFEST_NAMES
|
||||
if (root / name).is_file()
|
||||
)
|
||||
key = str((root, manifest_mtimes))
|
||||
if key in _WORKSPACE_PACKAGE_CACHE:
|
||||
return _WORKSPACE_PACKAGE_CACHE[key]
|
||||
|
||||
packages: dict[str, Path] = {}
|
||||
for pattern in _workspace_globs(root / "pnpm-workspace.yaml"):
|
||||
for pattern in _workspace_globs(root):
|
||||
package_dirs: list[Path] = [root] if pattern in (".", "./") else list(root.glob(pattern))
|
||||
for package_dir in package_dirs:
|
||||
manifest = package_dir / "package.json"
|
||||
|
||||
@@ -459,6 +459,79 @@ def test_pnpm_workspace_package_import_resolves_package_entry(tmp_path: Path):
|
||||
assert _has_edge(result, "apps/web/src/page.ts", "packages/types/src/index.ts")
|
||||
|
||||
|
||||
def test_npm_workspace_package_import_resolves_package_entry(tmp_path: Path):
|
||||
_write(
|
||||
tmp_path / "package.json",
|
||||
json.dumps({"workspaces": ["apps/*", "packages/*"]}),
|
||||
)
|
||||
_write(
|
||||
tmp_path / "packages/types/package.json",
|
||||
json.dumps({"name": "@workspace/types", "exports": "./src/index.ts"}),
|
||||
)
|
||||
target = _write(
|
||||
tmp_path / "packages/types/src/index.ts",
|
||||
"export interface SomeDto { id: string }\n",
|
||||
)
|
||||
importer = _write(
|
||||
tmp_path / "apps/web/src/page.ts",
|
||||
"import type { SomeDto } from '@workspace/types'\nconst dto: SomeDto = { id: '1' }\n",
|
||||
)
|
||||
|
||||
result = _extract_for([target, importer], tmp_path)
|
||||
|
||||
assert _has_edge(result, "apps/web/src/page.ts", "packages/types/src/index.ts")
|
||||
|
||||
|
||||
def test_yarn_workspace_package_import_resolves_package_entry(tmp_path: Path):
|
||||
_write(
|
||||
tmp_path / "package.json",
|
||||
json.dumps({"workspaces": {"packages": ["apps/*", "packages/*"]}}),
|
||||
)
|
||||
_write(
|
||||
tmp_path / "packages/types/package.json",
|
||||
json.dumps({"name": "@workspace/types", "exports": "./src/index.ts"}),
|
||||
)
|
||||
target = _write(
|
||||
tmp_path / "packages/types/src/index.ts",
|
||||
"export interface SomeDto { id: string }\n",
|
||||
)
|
||||
importer = _write(
|
||||
tmp_path / "apps/web/src/page.ts",
|
||||
"import type { SomeDto } from '@workspace/types'\nconst dto: SomeDto = { id: '1' }\n",
|
||||
)
|
||||
|
||||
result = _extract_for([target, importer], tmp_path)
|
||||
|
||||
assert _has_edge(result, "apps/web/src/page.ts", "packages/types/src/index.ts")
|
||||
|
||||
|
||||
def test_pnpm_workspace_takes_precedence_over_package_json_workspaces(tmp_path: Path):
|
||||
_write(
|
||||
tmp_path / "pnpm-workspace.yaml",
|
||||
"packages:\n - 'apps/*'\n - 'packages/*'\n",
|
||||
)
|
||||
_write(
|
||||
tmp_path / "package.json",
|
||||
json.dumps({"workspaces": ["other/*"]}),
|
||||
)
|
||||
_write(
|
||||
tmp_path / "packages/types/package.json",
|
||||
json.dumps({"name": "@workspace/types", "exports": "./src/index.ts"}),
|
||||
)
|
||||
target = _write(
|
||||
tmp_path / "packages/types/src/index.ts",
|
||||
"export interface SomeDto { id: string }\n",
|
||||
)
|
||||
importer = _write(
|
||||
tmp_path / "apps/web/src/page.ts",
|
||||
"import type { SomeDto } from '@workspace/types'\nconst dto: SomeDto = { id: '1' }\n",
|
||||
)
|
||||
|
||||
result = _extract_for([target, importer], tmp_path)
|
||||
|
||||
assert _has_edge(result, "apps/web/src/page.ts", "packages/types/src/index.ts")
|
||||
|
||||
|
||||
def test_js_import_resolution_ignores_stale_importer_cache_when_target_appears(tmp_path: Path):
|
||||
importer = _write(
|
||||
tmp_path / "src/lib/page.ts",
|
||||
|
||||
Reference in New Issue
Block a user