In a Dart pub workspace, when `api_client` declares `core: ^2.0.0`, which copy of `core` does it use inside the workspace and after publishing?
answer
- the local member wins
- whatever source is declared
- the constraint must still match
- outside: the hosted source again
- no path dependency needed
basics
~20 sInside the workspace api_client resolves to the local packages/core, whatever source the dependency names, as long as that copy's version satisfies ^2.0.0. Once api_client is published, its users get core from pub.dev as an ordinary hosted dependency.
solid answer
~40 sMembers that depend on each other always resolve to the copy in the workspace, regardless of the dependency's declared source. So `api_client` can declare a plain hosted `core: ^2.0.0`: during development pub uses the local `packages/core`, edits show up without publishing, and no `path:` dependency is needed. The local copy must still satisfy the constraint - if `core`'s pubspec says `version: 3.0.0`, resolution fails. Outside the workspace the declared source applies again, so someone depending on a published `api_client` gets the hosted `core` as a transitive dependency. That keeps every pubspec publishable, since pub.dev accepts only hosted and SDK dependencies. `dart pub publish` acts on the package in the current directory; `dart pub -C packages/core publish` targets one member, and `core` should go out before the `api_client` version that needs it.
code
yaml · 14 lines# packages/api_client/pubspec.yaml
name: api_client
version: 1.3.0
resolution: workspace
environment:
sdk: ^3.13.0
dependencies:
# A normal hosted constraint: inside the workspace pub uses
# packages/core (its version must satisfy ^2.0.0); consumers of
# the published api_client get core from pub.dev.
core: ^2.0.0
http: ^1.2.0go deeper
Recall that members depending on each other use the local copy inside the workspace, so edits appear without publishing.
Explain that the local copy replaces the source but not the constraint, and that the declared hosted source applies again once a package is consumed outside the workspace.
Plan releases: keep pubspecs publishable, publish dependencies before dependents, and remember that in-workspace tests don't prove what consumers resolve from pub.dev.
Decide which members are published and which stay internal, since every published member turns local version bumps into a public compatibility promise.
## The rule In a pub workspace, several packages share one resolution. When one **member** depends on another member, pub resolves that dependency to **the local copy in the workspace, regardless of the source the dependency declares**. dart.dev's example is a `client_package` that depends on `shared: ^2.3.0`: inside the workspace the local `shared` is used. Take a monorepo with an app, `apps/field_app`, and four local packages: `core`, `api_client`, `ui_kit` and `storage`. If `api_client` declares ```yaml dependencies: core: ^2.0.0 ``` that line reads like an ordinary **hosted** dependency on pub.dev. Inside the workspace, pub nevertheless uses `packages/core` from disk. Edit a function in `core` and `api_client` sees it at once - no publish, no version bump, no path dependency. ## The constraint must still match The local copy replaces the *source*, not the *constraint*. dart.dev is explicit that the local version still has to satisfy the declared range: - `core` at `version: 2.4.0` and a constraint of `^2.0.0` - resolves to the local copy. - `core` bumped to `version: 3.0.0` while `api_client` still says `^2.0.0` - `dart pub get` fails for the whole workspace, because the one resolution has no solution. So a breaking bump in `core` forces every dependent member to raise its constraint in the same change, which is the point: the monorepo never builds against a combination it has not agreed on. ## Why no path dependency | | `path: ../core` dependency | workspace member with a hosted constraint | |---|---|---| | Local edits visible immediately | yes | yes | | Can the dependent package be published to pub.dev? | no - pub.dev accepts only hosted and SDK dependencies | yes - the pubspec already names a hosted source | | Lockfiles | one per package, so third-party versions can drift | one shared `pubspec.lock` for everyone | | What a consumer of the published package gets | not applicable | the hosted `core` from pub.dev | The workspace lets development use local code while the pubspec describes what consumers will get. ## After publishing Outside the workspace the declared source applies again. If `api_client` is published and someone else depends on it, their resolution fetches `core` from pub.dev as a **transitive hosted dependency** - the local copy in your repository is invisible to them. Two consequences: 1. Every `core` version that a published `api_client` requires must itself be on pub.dev. Publish **`core` first**, then `api_client`. 2. Tests passing inside the workspace prove only the local combination. What consumers get depends on the hosted versions their constraints allow. ## Publishing one member - `dart pub publish` publishes **the package in the current directory**; in a workspace it does not publish all members. - `dart pub -C packages/core publish` is the same as changing into `packages/core` and publishing. - A freshly published version can take a few minutes to become available on pub.dev. When the second package depends on it, dart.dev suggests waiting, or using `--skip-validation` for the second publish. - Members never meant for pub.dev, such as `field_app`, should carry `publish_to: none`. ## Adding a fifth package When the team adds `packages/sync` to the monorepo, the member-to-member rule decides how it is wired: 1. Create `packages/sync/pubspec.yaml` with a `name`, a `version`, `resolution: workspace` and an SDK constraint of `^3.6.0` or higher. 2. If the root uses the `packages/*` glob (Dart 3.11+), nothing else is needed at the root; otherwise add the path to the root `workspace` list. 3. In each member that uses it, declare `sync` with an ordinary constraint that its local `version` satisfies, such as `sync: ^0.1.0`. 4. If `sync` is internal, give it `publish_to: none`; if it will be published, keep its own dependencies hosted. 5. Run `dart pub get` anywhere in the repository; the shared `pubspec.lock` at the root now includes `sync` and its dependencies. ## Summary - Member-to-member dependencies resolve to the local copy, whatever source they declare. - The local version must still satisfy the constraint. - Outside the workspace, the hosted source is used, so publish dependencies before their dependents.
- Why not connect the packages with `path: ../core` dependencies instead of a workspace?Path dependencies make local edits visible too, but a package that has one can't be published to pub.dev, and each package still resolves separately with its own lockfile, so third-party versions drift. A workspace keeps hosted constraints in every pubspec and one shared resolution.
- You bump `core` to 3.0.0 in the repository. What breaks, and how do you fix it?Every member still declaring `core: ^2.x` no longer matches the local version, so `dart pub get` fails for the whole workspace. Raise those constraints to `^3.0.0` and adapt their code in the same change; the failure is the workspace forcing the repository to agree on one `core`.
- Does running `dart pub publish` at the repository root publish every member?No. It acts on the package in the current directory - at the root, the root package, which normally has `publish_to: none`. Publish members one by one, from their directories or with `dart pub -C packages/<name> publish`, dependencies first.
saying these in an interview costs you the question
- Workspace members must reference each other with path dependencies
- Inside a workspace the local copy is used even if its version breaks the constraint
- Users of a published api_client also get the local core from your repository
- dart pub publish at the workspace root publishes every member at once
- dart pub add at the root adds the dependency to every member