Files
dajiaohuang 47ddc5c47b fix(imports): pick workspace exports by importer platform, runtime before types
The workspace `exports` resolver reduced a condition object to a single target,
so a package that splits its runtime source from its declarations never
produced a cross-package edge. Reported in #3487; the exports map itself is
present and works, the defect is in how one is chosen.

`_EXPORT_CONDITION_PRIORITY` listed `types` ahead of `require` and `default`.
`types` is declaration-only and normally points into `dist/`, which is build
output and not part of the corpus, so the resolver returned a path that is not
on disk and stopped there — `_package_entry_candidates` returned `[candidate]`
as soon as any condition matched, so the `default` source beside it was never
tried and the import became a dangling `ref_*` external.

That is not the order the map was added with. #1308 specifies
`source -> import -> module -> default -> require -> types`, and the commit
that implemented it says `default` is consulted LAST so a matching
`import`/`module`/`svelte` condition wins; the tuple shipped with `types` in
the middle instead.

Three changes, all on the workspace `exports` path:

- `types` moves to the end of `_EXPORT_CONDITION_PRIORITY`, restoring the
  specified order. `require`/`default` keep their relative order, so the
  behaviour pinned by `test_workspace_subpath_export_default_consulted_last`
  is unchanged.
- `_resolve_export_targets` keeps every condition target in preference order
  instead of returning the first, so a target that is not on disk falls
  through to the next condition rather than taking the import down with it.
- `react-native` is honoured only for an importer that is itself native, and
  each target is tried alongside its platform-suffixed siblings
  (`Icon.tsx` -> `Icon.web.tsx` / `Icon.native.tsx`), which is how a bundler
  reaches a platform split whose `exports` target names the unsuffixed path.
  Importer platform comes from whole path segments, so `apps/mobile/src` is
  native while `packages/web-utils` is not web.

`_resolve_export_target` keeps its signature and its semantics for the
`package.json` `imports` path, and platform expansion applies only to the
workspace `exports` path, so relative imports elsewhere are untouched.

Behaviour note for review: when every `exports` target escapes the package
directory, resolution still falls through to the bare-path fallback exactly as
before. The first revision of this change swallowed that fallback, which the
containment guard made reachable, and a test now pins it. The `"."` branch
additionally gains the containment guard its subpath sibling already had; that
is the only change here beyond the reported defect, and such a package
previously resolved outside its own directory.

Verified: same 29 pre-existing failures before and after, byte-identical
failure sets (`diff` clean; unrelated to this path — non-regular-file, watch
and uninstall-scope tests on Windows). Passing goes 5500 -> 5507, the seven
tests added here. The fixture from #3487 resolves its four previously-dropped
imports and leaves the three control cases unchanged; focused run 109 passed;
`ruff check` clean; all five `tools.skillgen` CI-parity checks OK.

(cherry picked from commit c704a5b2f2)
2026-09-12 21:31:35 +01:00
..
2026-09-07 20:21:35 +01:00