├── README.md ├── text ├── 0000-template.md ├── 0004-config-dependencies.md ├── 0002-time-based-resolution-mode.md └── 0001-catalogs.md └── CODE_OF_CONDUCT.md /README.md: -------------------------------------------------------------------------------- 1 | # pnpm RFCs 2 | -------------------------------------------------------------------------------- /text/0000-template.md: -------------------------------------------------------------------------------- 1 | # {{TITLE: a human-readable title for this RFC!}} 2 | 3 | ## Summary 4 | 5 | {{A concise, one-paragraph description of the change.}} 6 | 7 | ## Motivation 8 | 9 | {{Why are we doing this? What pain points does this resolve? What use cases does it support? What is the expected outcome? Use real, concrete examples to make your case!}} 10 | 11 | ## Detailed Explanation 12 | 13 | {{Describe the expected changes in detail, }} 14 | 15 | ## Rationale and Alternatives 16 | 17 | {{Discuss 2-3 different alternative solutions that were considered. This is required, even if it seems like a stretch. Then explain why this is the best choice out of available ones.}} 18 | 19 | ## Implementation 20 | 21 | {{Give a high-level overview of implementation requirements and concerns. Be specific about areas of code that need to change, and what their potential effects are. Discuss which repositories and sub-components will be affected, and what its overall code effect might be.}} 22 | 23 | {{THIS SECTION IS REQUIRED FOR RATIFICATION -- you can skip it if you don't know the technical details when first submitting the proposal, but it must be there before it's accepted}} 24 | 25 | ## Prior Art 26 | 27 | {{This section is optional if there are no actual prior examples in other tools}} 28 | 29 | {{Discuss existing examples of this change in other tools, and how they've addressed various concerns discussed above, and what the effect of those decisions has been}} 30 | 31 | ## Unresolved Questions and Bikeshedding 32 | 33 | {{Write about any arbitrary decisions that need to be made (syntax, colors, formatting, minor UX decisions), and any questions for the proposal that have not been answered.}} 34 | 35 | {{THIS SECTION SHOULD BE REMOVED BEFORE RATIFICATION}} 36 | 37 | -------------------------------------------------------------------------------- /text/0004-config-dependencies.md: -------------------------------------------------------------------------------- 1 | # Config dependencies 2 | 3 | ## Summary 4 | 5 | A new type of dependencies for installing configuration and hooks. 6 | 7 | ## Motivation 8 | 9 | We want to make it possible to share some configurations between projects. Storing the configuration in regular dependencies is not an option as we might need the configuration during installation of the "regular" dependencies ("dependencies", "devDependencies", and "optionalDependencies"). Hence, we need to install these config dependencies before other types of dependencies. 10 | 11 | Some examples of usage: 12 | 13 | * Installing hooks used by [`.pnpmfile.cjs`](https://pnpm.io/pnpmfile). 14 | * Installing the list of dependencies that are allowed to be built (the [pnpm.onlyBuiltDependenciesFile](https://pnpm.io/package_json#pnpmonlybuiltdependenciesfile)). 15 | * Installing [catalogs](https://pnpm.io/catalogs). 16 | * Loading patch file. 17 | 18 | ## Detailed Explanation 19 | 20 | There will be a new field in `package.json` called `pnpm.configDependencies`. For example: 21 | 22 | ```json 23 | { 24 | "name": "my-pkg", 25 | "version": "0.0.0", 26 | "pnpm": { 27 | "configDependencies": { 28 | "my-configs": "1.0.0+sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==" 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | These new type of "config" dependencies will be npm packages with a lot of limitations: 35 | 36 | * They won't have any dependencies. Even if they will have dependencies, pnpm will ignore them during installation. 37 | * They will not have lifecycle scripts. 38 | * They will only be installable via exact versions. 39 | 40 | These dependencies will be installed into a new hidden directory inside `node_modules`: `node_modules/.pnpm-config`. 41 | 42 | ## Rationale and Alternatives 43 | 44 | The `"onlyBuiltDependencies"` list can currently be loaded from `node_modules`. However, this introduces "works on my machine" issues since the list isn't available during the link-from-store stage. As a result, if the dependency is not allowed to be built, it might still be loaded from the side-effects cache. 45 | 46 | ## Implementation 47 | 48 | These dependencies will be installed as early as possible in order to be able to load settings from it. So this should happen ouside of the `@pnpm/core` module. When we call `mutateModules` we should already have all the configurations from `pnpm.configDependencies` loaded and ready. 49 | 50 | ## Prior Art 51 | 52 | There was a big RFC by Brandon Cheng about a much more powerful system using [templates](https://github.com/pnpm/rfcs/pull/3), which was rejected. 53 | 54 | ## Unresolved Questions and Bikeshedding 55 | 56 | {{Write about any arbitrary decisions that need to be made (syntax, colors, formatting, minor UX decisions), and any questions for the proposal that have not been answered.}} 57 | 58 | {{THIS SECTION SHOULD BE REMOVED BEFORE RATIFICATION}} 59 | 60 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at coc@pnpm.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /text/0002-time-based-resolution-mode.md: -------------------------------------------------------------------------------- 1 | # Time-based resolution mode 2 | 3 | ## Summary 4 | 5 | A new resolution mode that resolves versions not to their highest versions but to versions that were published in a given time frame. 6 | 7 | ## Motivation 8 | 9 | The resolution mode that is currently used by pnpm is not optimized for caching. During installation we need to always fetch the metadata of each package as we don't know if the metadata in the cache has the highest version of the package. Also, installation of the highest version is dangerous as someone may hijack a subdependency, publish a new version, and pnpm will immediately install it. 10 | 11 | ## Detailed Explanation 12 | 13 | Resolution will be divided into two stages. The first stage will resolve all the direct dependencies of all the workspace projects. This stage may work the same way as it works now (highest version is picked). When all the direct dependencies are resolved, we check when were the picked versions released. This information is present in the package metadata at the "time" field. For example, if we install webpack and eslint, we get webpack resolved to v5.74.0 and eslint resolved to v8.22.0. `webpack@5.74.0` was released at "2022-07-25T08:00:33.823Z". `eslint@8.22.0` was released at "2022-08-14T01:23:41.730Z". Now we compare the dates of each released packages and pick the nearest date. In this case, the nearest date is the date eslint was released: "2022-08-14T01:23:41.730Z". We add some delta (e.g. 1 hour) to this time and call the resulting date T. The delta is needed because in some monorepos packages may be published in random order, so dependent packages may be released after dependency packages. 14 | 15 | At the second stage, we resolve all the subdependencies. At this stage, instead of resolving a range to the highest available version, we filter out any versions that were released after T and pick the highest version from those. Let's say we need to resolve `ajv@^6.10.0`. We already have a metadata of ajv in cache and it was saved after T, so we don't need to redownload it. This are the versions of ajv that match `^6.10.0`: 16 | 17 | | version | release date | 18 | |--|--| 19 | | 6.10.0 | 2021-01-08 | 20 | | 6.10.1 | 2021-12-22 | 21 | | 6.10.2 | 2022-01-25 | 22 | | 6.11.0 | 2022-04-05 | 23 | | 6.11.1 | 2022-08-16 | 24 | 25 | As you can see, v6.11.1 was released after T, so we pick v6.11.0, which is the highest version that satisfies the range and the time frame. 26 | 27 | ## Rationale and Alternatives 28 | 29 | An alternative solution might be to resolve to the lowest version. This is a resolution mode that [Maël Nison](https://github.com/arcanis) came up with and he already works on the [implementation in Yarn](https://github.com/yarnpkg/berry/pull/4351). It is called "stable" resolution mode. This resolution mode is harder to implement in pnpm. pnpm starts to fetch a package immediately after resolving it. This is fine with "hightest" resolution mode because different ranges are resolved to the same highest version. But when the lowest version is picked, the package manager should do some deduplication before starting the fetching process. For instance, in the root, `foo@^1.0.0` might be in dependencies. pnpm will immediately resolve it to `1.0.0` and fetch it from the registry. But then in the subdependencies there might be `foo@^1.1.0` in the dependencies, so pnpm will need to download a second version of foo even though 1.1.0 would have satisfied the root as well. This is easier to do with Yarn, which has a separate stage for resolution. 30 | 31 | ## Implementation 32 | 33 | {{Give a high-level overview of implementation requirements and concerns. Be specific about areas of code that need to change, and what their potential effects are. Discuss which repositories and sub-components will be affected, and what its overall code effect might be.}} 34 | 35 | {{THIS SECTION IS REQUIRED FOR RATIFICATION -- you can skip it if you don't know the technical details when first submitting the proposal, but it must be there before it's accepted}} 36 | 37 | ## Prior Art 38 | 39 | {{This section is optional if there are no actual prior examples in other tools}} 40 | 41 | {{Discuss existing examples of this change in other tools, and how they've addressed various concerns discussed above, and what the effect of those decisions has been}} 42 | 43 | ## Unresolved Questions and Bikeshedding 44 | 45 | {{Write about any arbitrary decisions that need to be made (syntax, colors, formatting, minor UX decisions), and any questions for the proposal that have not been answered.}} 46 | 47 | {{THIS SECTION SHOULD BE REMOVED BEFORE RATIFICATION}} 48 | 49 | -------------------------------------------------------------------------------- /text/0001-catalogs.md: -------------------------------------------------------------------------------- 1 | # Catalogs: Shareable dependency version specifiers 2 | 3 | ## Summary 4 | 5 | "_Catalogs_" allow multiple `package.json` files to share the same version specifier of a dependency through a new `catalog:` protocol. 6 | 7 | A catalog may be defined in `pnpm-workspace.yaml`: 8 | 9 | ```yaml 10 | packages: 11 | - packages/* 12 | 13 | catalog: 14 | react: ^18.2.0 15 | react-dom: ^18.2.0 16 | redux: ^4.2.0 17 | react-redux: ^8.0.0 18 | ``` 19 | 20 | A package referencing the catalog above will have the following on-disk and in-memory representations. 21 | 22 | **On-Disk** 23 | 24 | ```json 25 | { 26 | "name": "@example/react-components", 27 | "dependencies": { 28 | "react": "catalog:", 29 | "redux": "catalog:" 30 | } 31 | } 32 | ``` 33 | 34 | **In-Memory and Publish Time** 35 | 36 | ```json 37 | { 38 | "name": "@example/react-components", 39 | "dependencies": { 40 | "react": "^18.2.0", 41 | "redux": "^4.2.0", 42 | } 43 | } 44 | ``` 45 | 46 | ## Motivation 47 | 48 | ### Synchronization 49 | 50 | A common workflow in monorepos is the need to synchronize on the same version of a dependency. 51 | 52 | For example, the `foo` and `bar` packages of a monorepo may declare the same version of `react` in their `package.json` files. 53 | 54 | ```json 55 | { 56 | "name": "@monorepo/foo", 57 | "dependencies": { 58 | "react": "^18.2.0", 59 | } 60 | } 61 | ``` 62 | 63 | ```json 64 | { 65 | "name": "@monorepo/bar", 66 | "dependencies": { 67 | "react": "^18.2.0", 68 | } 69 | } 70 | ``` 71 | 72 | For smaller monorepos with a few packages, it's easy to manually ensure these versions stay in sync. For monorepos with hundreds of packages and many contributors, it becomes untenable to rely on code review to ensure every dependency has a single version for all packages. 73 | 74 | As a result, multiple versions of the same dependency appear over time. This can cause different flavors of surprising problems. 75 | 76 | - In projects that bundle dependencies, multiple versions inflate the size of the final result deployed to users. 77 | - Differing versions result in multiple copies that may not interact well at runtime, especially if features like [`Symbol()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) are used. For example, [React hooks will error if a component is rendered with a different copy of React in the same app](https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react). 78 | - For TypeScript, multiple copies of the same `@types` package causes compile errors from mismatching definitions. The compiler diagnostic for this is usually: *"argument of type `Foo` is not assignable to parameter of type `Foo`"*. For developers that have seen this before, they may realize this diagnostic is due to a dependency version mismatch. For developers new to TypeScript, *"`Foo` is not assignable to `Foo`"* is very confusing. 79 | 80 | While there are situations differing versions are intentional, this is more often accidental. Multiple differing versions arise from not reviewing `pnpm-lock.yaml` file changes or not searching for existing dependency specifiers before adding a new one. The later is typically unwritten convention in most monorepos. 81 | 82 | ### Merge Conflict Resistance 83 | 84 | In addition to reducing the likelihood of multiple versions of the same dependency in a monorepo, the new `catalog:` protocol reduces merge conflicts. Any `package.json` files using the catalog protocol to declare a dependency do not need to be edited when changing the version of that dependency. This side steps `package.json` merge conflicts by avoiding edits to them in the first place. 85 | 86 | **Merge conflict resistance is a primary motivator for introducing catalogs as a first-class feature to pnpm.** This is only possible through a new specifier protocol. See [_What kinds of merge conflicts are avoided?_](#what-kinds-of-merge-conflicts-are-avoided) for details. 87 | 88 | ## Detailed Explanation 89 | 90 | ### Configuring Catalogs 91 | 92 | Catalogs are configured in `pnpm-workspace.yaml` and available to all workspace packages. 93 | 94 | A default or unnamed catalog can be specified using the `catalog` config. 95 | 96 | ```yaml 97 | packages: 98 | - packages/* 99 | 100 | # These dependencies can be referenced through "catalog:default" or "catalog:" 101 | catalog: 102 | jest: ^29.6.1 103 | redux: ^4.2.0 104 | react-redux: ^8.0.0 105 | ``` 106 | 107 | Additionally, named catalogs can be created by adding a `catalogs` config. Any named catalog will be available for reference through the `catalog:` version specifier protocol. 108 | 109 | ```yaml 110 | packages: 111 | - packages/* 112 | 113 | # Can be referenced through "catalog:default" or "catalog:" 114 | catalog: 115 | jest: ^29.6.1 116 | redux: ^4.2.0 117 | react-redux: ^8.0.0 118 | 119 | catalogs: 120 | # Can be referenced through "catalog:react17" 121 | react17: 122 | react: ^17.0.2 123 | react-dom: ^17.0.2 124 | 125 | # Can be referenced through "catalog:react18" 126 | react18: 127 | react: ^18.2.0 128 | react-dom: ^18.2.0 129 | ``` 130 | 131 | The default catalog specified directly under `catalog` has special treatment; package authors can specify `catalog:` if they prefer conciseness, or `catalog:default` for explicitness. Attempting to create a named catalog of `default` under `catalogs` will throw an error. 132 | 133 | ### What kinds of merge conflicts are avoided? 134 | 135 | Suppose a git commit upgrades the version of a commonly used dependency for all workspace packages. Suppose another git commit at the same time is attempting to perform any of the following: 136 | 137 | 1. Change the version of a dependency line-adjacent to the upgraded dependency. 138 | 2. Add or remove a dependency line-adjacent to the upgraded dependency. 139 | 3. Add a new declaration of the upgraded dependency. 140 | 141 | Scenarios 1 and 2 will result in a git merge conflict that prevents these two commits from merging. This will not be the case when using the `catalog:` protocol. 142 | 143 | Scenario 3 will result in an inconsistent/lockfile, which is not prevented by this RFC. 144 | 145 | ## Rationale for First-Class Support 146 | 147 | While there's existing tooling in the frontend ecosystem for reusing versions ([`syncpack`](https://github.com/JamieMason/syncpack) being one great option), builtin support from pnpm allows several improvements not otherwise possible. 148 | 149 | ### 1. Eliminates a Synchronization Build Step 150 | 151 | As mentioned in [_Motivations — Merge Conflict Resistance_](#merge-conflict-resistance), the new `catalog:` protocol enables `package.json` files to remain unchanged when upgrading or downgrading a dependency. Existing approaches typically synchronize `package.json` dependencies by editing them in bulk. 152 | 153 | For example, developers may find + replace `"react": "^18.1.0"` to `"react": "^18.2.0"` across a repository. 154 | 155 | - These giant edits lead to churn in `package.json` files and merge conflicts with other commits editing `package.json` files. 156 | - Repository maintainers have to remember to run this synchronization step periodically, or create a Continuous Integration step to verify the repo is in a good state. 157 | 158 | Neither of these error-prone operations are necessary when using a catalog. 159 | 160 | ### 2. Intention becomes clear 161 | 162 | There might be a tight relationship between `foo` and `bar`. 163 | 164 | ```json5 165 | { 166 | "name": "@monorepo/foo", 167 | "dependencies": { 168 | "react": "^17.0.2" 169 | } 170 | } 171 | ``` 172 | 173 | ```json5 174 | { 175 | "name": "@monorepo/bar", 176 | "dependencies": { 177 | "react": "^17.0.2" 178 | } 179 | } 180 | ``` 181 | 182 | A developer working primarily in `@monorepo/bar` may not realize the implied coupling and upgrade `@monorepo/bar` to `react@18` without realizing an edit to `@monorepo/foo` was also required. 183 | 184 | The `catalog:` protocol makes it more clear from just reading `package.json` when a dependency is intended to be consistent across the monorepo. Ideally this person would search "_catalog package.json_" online and find pnpm.io docs. 185 | 186 | ## Implementation 187 | 188 | ### Lockfile 189 | 190 | Catalogs will be saved to `pnpm-lock.yaml` under a new `catalogs` key. This allows users to more easily review changes to catalogs and pnpm to perform faster up-to-date checks. 191 | 192 | ```yaml 193 | lockfileVersion: next 194 | 195 | importers: 196 | 197 | packages/foo: 198 | dependencies: 199 | react: 200 | specifier: 'catalog:' 201 | version: ^18.2.0 202 | 203 | catalogs: 204 | 205 | default: 206 | react: 207 | specifier: ^18.2.0 208 | react-dom: 209 | specifier: ^18.2.0 210 | redux: 211 | specifier: ^4.2.0 212 | react-redux: 213 | specifier: ^8.0.0 214 | 215 | packages: 216 | # ... 217 | ``` 218 | 219 | ### Other Changes 220 | 221 | Similar to the [`workspace:` protocol](https://pnpm.io/workspaces#workspace-protocol-workspace), `pnpm publish` will need to replace instances of `catalog:` with valid specifiers before publishing. 222 | 223 | ### Add and update commands 224 | 225 | The `pnpm add` command will add versions from default catalog if it's configured. The `pnpm update` command will prompt users if they wish to update specifiers in a catalog. 226 | 227 | ## Extensions 228 | 229 | ### Merge Conflict Resistance in the Lockfile 230 | 231 | Although `package.json` files do not need to be updated when a catalog version changes, the `pnpm-lock.yaml` file will continue to require updates to affected `importers` entries. 232 | 233 | ```yaml 234 | lockfileVersion: next 235 | 236 | importers: 237 | 238 | packages/foo: 239 | dependencies: 240 | react: 241 | specifier: 'catalog:default' 242 | version: 18.2.0 # ← If a user changes the catalog entry for react, this field needs to change. 243 | 244 | catalogs: 245 | 246 | default: 247 | react: 248 | specifier: ^18.2.0 249 | 250 | packages: 251 | ``` 252 | 253 | This means it's still possible for the `pnpm-lock.yaml` file to end up in an inconsistent/broken state after two git commits merge. This is technically a "merge conflict", but not one in the scope of git to detect. 254 | 255 | ```diff 256 | --- a/pnpm-lock.yaml 257 | +++ b/pnpm-lock.yaml 258 | lockfileVersion: next 259 | 260 | importers: 261 | 262 | packages/foo: 263 | dependencies: 264 | react: 265 | specifier: 'catalog:default' 266 | version: 18.2.0 267 | 268 | + packages/bar: 269 | + dependencies: 270 | + react: 271 | + specifier: 'catalog:default' 272 | + version: 17.0.1 # ← This field is incorrect 273 | + 274 | catalogs: 275 | 276 | default: 277 | react: 278 | specifier: ^18.2.0 279 | 280 | packages: 281 | ``` 282 | 283 | The RFC in its current state describes how `catalog:` significantly reduces merge conflicts to `package.json` files. There are ways to reduce merge conflicts and churn in `pnpm-lock.yaml`, but not in a known way that's simple. 284 | 285 | One option that may be explored is further normalization by the recording resolved concrete versions to the `catalogs` portion of the lockfile. 286 | 287 | ```yaml 288 | lockfileVersion: next 289 | 290 | importers: 291 | 292 | packages/foo: 293 | dependencies: 294 | react: 295 | specifier: 'catalog:default' 296 | version: 'catalog:default' 297 | 298 | packages/bar: 299 | dependencies: 300 | react: 301 | specifier: 'catalog:default' 302 | version: 'catalog:default' 303 | 304 | catalogs: 305 | 306 | default: 307 | react: 308 | specifier: ^18.2.0 309 | version: 18.2.0 310 | 311 | packages: 312 | ``` 313 | 314 | The problem with the format above is that it does not represent [peer dependencies](https://nodejs.org/en/blog/npm/peer-dependencies#using-peer-dependencies) well: 315 | 316 | ```yaml 317 | lockfileVersion: next 318 | 319 | importers: 320 | 321 | packages/foo: 322 | dependencies: 323 | chai: 324 | specifier: 'catalog:default' 325 | version: 'catalog:default' 326 | chai-as-promised: 327 | specifier: 'catalog:default' 328 | # Replacing ↓ with catalog:default would remove valuable information. 329 | version: 7.1.1(chai@4.3.8) 330 | 331 | catalogs: 332 | 333 | default: 334 | chai: 335 | specifier: ^4.3.8 336 | version: 4.3.8 337 | chai-as-promised: 338 | specifier: ^7.1.1 339 | version: 7.1.1 340 | 341 | packages: 342 | /chai-as-promised@7.1.1(chai@4.3.8): 343 | # ... 344 | 345 | /chai@4.3.8: 346 | # ... 347 | ``` 348 | 349 | ### Constraints 350 | 351 | Catalog versions alone are insufficient to completely prevent multiple versions of the same dependency. Dependencies of dependencies (transitive dependencies) may pull in a package that already exists elsewhere in the dependency graph, but on a different version. 352 | 353 | For example, the `foo`, `bar`, and `baz` workspace packages may all be using a catalog version for `react` on `^18.2.0`. However, `baz` may depend on `quz-react-components`, which brings in an older version of React. 354 | 355 | ```mermaid 356 | flowchart LR 357 | classDef project color:#333333,fill:#bdd5fc,stroke:#333,stroke-width:1px 358 | classDef package color:#333333,fill:#e5fce7,stroke:#05a000,stroke-width:1px 359 | classDef node_modules color:#333333,fill:#f5f5f5,stroke:#999,stroke-width:1px 360 | 361 | foo:::project 362 | bar:::project 363 | baz:::project 364 | 365 | node_modules:::node_modules 366 | 367 | foo --> react 368 | bar --> react 369 | baz --> react 370 | 371 | baz --> quz-react-components --> react16 372 | 373 | subgraph node_modules 374 | react["react@18.2.0"]:::package 375 | quz-react-components["quz-react-components@0.1.0"]:::package 376 | react16["react@16.14.0"]:::package 377 | end 378 | ``` 379 | 380 | This scenario currently requires human intervention and is outside of the scope of this RFC. It's possible a future RFC proposal would address this. Such a new proposal would need to propose a configuration syntax for enforcing a single version throughout the full dependency graph. 381 | 382 | ## Alternatives 383 | 384 | ### Syncpack 385 | 386 | [Syncpack](https://github.com/JamieMason/syncpack/) is a great open source tool for keeping `package.json` dependency specifiers in sync on disk. 387 | 388 | The proposed solution allows metadata to be defined in a singular file without copying definitions to other files on disk. This is a capability only possible by the package manager reading `package.json` files. 389 | 390 | ### Comparison to overrides/resolutions 391 | 392 | An alternative mechanism for the version catalog is the [`pnpm.overrides` feature](https://pnpm.io/package_json#pnpmoverrides). While mechanically this allows you to set the version of a dependency across all workspace packages, it can be a bit unexpected if `pnpm.overrides` rewrites a dependency's dependency to an incompatible version silently. 393 | 394 | `pnpm.overrides` is ultimately intended for a different purpose. The NPM RFC for a similar feature explicitly states that it should be used as a short-term hack to fix vendor problems. 395 | 396 | > Using this feature should be considered a hack in most cases, something that is done temporarily while waiting for a bug to be fixed, or to avoid excessive duplication caused by an overly strict meta-dependency specifier. 397 | https://github.com/npm/rfcs/blob/main/accepted/0036-overrides.md 398 | 399 | The `catalog:` protocol is conversely intended for long-lived usage. 400 | 401 | ## Prior Art 402 | 403 | - The Gradle build tool has a [similar concept called _version catalogs_](https://docs.gradle.org/current/userguide/platforms.html#sub:central-declaration-of-dependencies). This RFC's name was [directly chosen as inspiration](https://github.com/pnpm/pnpm/issues/2713#issuecomment-1341476866). 404 | - Syncpack as mentioned above in the [alternatives section](#syncpack). 405 | - Rush has a ["Preferred versions" concept](https://rushjs.io/pages/advanced/preferred_versions/) that works similarly. 406 | - Rust allows users to define a [`[workspace.dependencies]` "_table_"](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table). Members of the workspace can then [inherit its entries](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace). 407 | --------------------------------------------------------------------------------