-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: catalog support #6884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: catalog support #6884
Changes from 11 commits
b4f879d
f3107d5
4ba1ebc
c1db43e
2e4e3ea
0659a86
fe756bd
ece129a
96d425f
2da3547
3675832
3e15ac8
7388c83
819ee0b
fc7c1d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| releases: | ||
| "@yarnpkg/cli": minor | ||
| "@yarnpkg/plugin-catalog": major | ||
| "@yarnpkg/plugin-pack": patch | ||
|
|
||
| declined: | ||
| - "@yarnpkg/plugin-compat" | ||
| - "@yarnpkg/plugin-constraints" | ||
| - "@yarnpkg/plugin-dlx" | ||
| - "@yarnpkg/plugin-essentials" | ||
| - "@yarnpkg/plugin-init" | ||
| - "@yarnpkg/plugin-interactive-tools" | ||
| - "@yarnpkg/plugin-nm" | ||
| - "@yarnpkg/plugin-npm" | ||
| - "@yarnpkg/plugin-npm-cli" | ||
| - "@yarnpkg/plugin-patch" | ||
| - "@yarnpkg/plugin-pnp" | ||
| - "@yarnpkg/plugin-pnpm" | ||
| - "@yarnpkg/plugin-stage" | ||
| - "@yarnpkg/plugin-typescript" | ||
| - "@yarnpkg/plugin-version" | ||
| - "@yarnpkg/plugin-workspace-tools" | ||
| - "@yarnpkg/builder" | ||
| - "@yarnpkg/core" | ||
| - "@yarnpkg/doctor" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| --- | ||
| category: features | ||
| slug: /features/catalogs | ||
| title: "Catalogs" | ||
| description: Centralize dependency version management across your workspace with reusable version catalogs. | ||
| --- | ||
|
|
||
| ## Overview | ||
|
|
||
| Catalogs provide centralized dependency version management for workspaces. Inspired by [pnpm's catalog functionality](https://pnpm.io/catalogs), they allow you to define version ranges in your `.yarnrc.yml` and reference them across multiple `package.json` files using the `catalog:` protocol. | ||
|
|
||
| This eliminates version duplication, ensures consistency across packages, and makes dependency upgrades much simpler — update one place instead of many files. | ||
|
|
||
| :::info | ||
| The catalog plugin is included by default starting from Yarn 4.10.0. | ||
| ::: | ||
|
|
||
| ### Basic usage | ||
|
|
||
| Define a catalog in your `.yarnrc.yml`: | ||
|
|
||
| ```yaml | ||
| catalog: | ||
| react: ^18.3.1 | ||
| lodash: ^4.17.21 | ||
| ``` | ||
|
|
||
| Reference entries in your `package.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "dependencies": { | ||
| "react": "catalog:", | ||
| "lodash": "catalog:" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Named catalogs | ||
|
|
||
| You can define multiple named catalogs using the `catalogs` field for different purposes: | ||
|
|
||
| ```yaml | ||
| catalog: | ||
| lodash: ^4.17.21 | ||
|
|
||
| catalogs: | ||
| react18: | ||
| react: ^18.3.1 | ||
| react-dom: ^18.3.1 | ||
| react17: | ||
| react: ^17.0.2 | ||
| react-dom: ^17.0.2 | ||
| ``` | ||
|
|
||
| Reference named catalogs by specifying the name: | ||
|
|
||
| ```json | ||
| { | ||
| "dependencies": { | ||
| "lodash": "catalog:", | ||
| "react": "catalog:react18" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Publishing | ||
|
|
||
| When publishing packages, the `catalog:` protocol is automatically replaced with actual version ranges, ensuring compatibility with other package managers: | ||
|
|
||
| ```json | ||
| // Source package.json | ||
| { | ||
| "dependencies": { | ||
| "react": "catalog:react18" | ||
| } | ||
| } | ||
|
|
||
| // Published package.json | ||
| { | ||
| "dependencies": { | ||
| "react": "^18.3.1" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Supported fields | ||
|
|
||
| The `catalog:` protocol works in `dependencies`, `devDependencies`, `peerDependencies`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # `@yarnpkg/plugin-catalog` | ||
|
|
||
| This plugin adds support for centralized dependency version management through catalogs, similar to pnpm's catalog feature. | ||
|
|
||
| It hooks into: | ||
| - `reduceDependency` and replaces catalog ranges with the ones defined in a catalog. | ||
| - `beforeWorkspacePacking` replacing catalogs with actual ranges before packing | ||
|
|
||
| ## Install | ||
|
|
||
| This plugin is included by default starting from Yarn 4.10.0. | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Default Catalog | ||
|
|
||
| Define a catalog in your `.yarnrc.yml`: | ||
|
|
||
| ```yaml | ||
| catalog: | ||
| react: ^18.0.0 | ||
| lodash: ^4.17.21 | ||
| ``` | ||
|
|
||
| Then reference catalog entries in your `package.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "dependencies": { | ||
| "react": "catalog:", | ||
| "lodash": "catalog:" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Named Catalogs | ||
|
|
||
| You can define multiple named catalogs for different purposes: | ||
|
|
||
| ```yaml | ||
| # Default catalog | ||
| catalog: | ||
| lodash: ^4.17.21 | ||
| typescript: ~4.9.0 | ||
|
|
||
| # Named catalogs | ||
| catalogs: | ||
| react18: | ||
| react: ^18.3.1 | ||
| react-dom: ^18.3.1 | ||
|
|
||
| react17: | ||
| react: ^17.0.2 | ||
| react-dom: ^17.0.2 | ||
|
|
||
| vue3: | ||
| vue: ^3.4.0 | ||
| vuex: ^4.1.0 | ||
| ``` | ||
|
|
||
| Then reference them in your `package.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "dependencies": { | ||
| "lodash": "catalog:", | ||
| "react": "catalog:react18", | ||
| "vue": "catalog:vue3" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The comprehensive feature documentation can be found in `packages/docusaurus/docs/features/catalog.mdx`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| { | ||
| "name": "@yarnpkg/plugin-catalog", | ||
| "version": "0.0.1", | ||
| "license": "BSD-2-Clause", | ||
| "main": "./sources/index.ts", | ||
| "exports": { | ||
| ".": "./sources/index.ts", | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "dependencies": { | ||
| "@yarnpkg/fslib": "workspace:^", | ||
| "tslib": "^2.4.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "@yarnpkg/core": "workspace:^", | ||
| "@yarnpkg/plugin-pack": "workspace:^" | ||
| }, | ||
| "devDependencies": { | ||
| "@yarnpkg/core": "workspace:^", | ||
| "@yarnpkg/plugin-pack": "workspace:^" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/yarnpkg/berry.git", | ||
| "directory": "packages/plugin-catalog" | ||
| }, | ||
| "scripts": { | ||
| "postpack": "rm -rf lib", | ||
| "prepack": "run build:compile \"$(pwd)\"" | ||
| }, | ||
| "publishConfig": { | ||
| "main": "./lib/index.js", | ||
| "exports": { | ||
| ".": "./lib/index.js", | ||
| "./package.json": "./package.json" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "/lib/**/*" | ||
| ], | ||
| "engines": { | ||
| "node": ">=18.12.0" | ||
| }, | ||
| "stableVersion": "0.0.1" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export const CATALOG_DESCRIPTOR_PREFIX = `catalog:`; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import {type Descriptor, type Locator, type Plugin, type Project, type Resolver, type ResolveOptions, type Workspace, SettingsType, structUtils} from '@yarnpkg/core'; | ||
| import {Hooks as CoreHooks} from '@yarnpkg/core'; | ||
| import {DEPENDENCY_TYPES, Hooks as PackHooks} from '@yarnpkg/plugin-pack'; | ||
|
|
||
| import {isCatalogReference, resolveDescriptorFromCatalog} from './utils'; | ||
|
|
||
| declare module '@yarnpkg/core' { | ||
| interface ConfigurationValueMap { | ||
| catalog: Map<string, string>; | ||
| catalogs: Map<string, Map<string, string>>; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| const plugin: Plugin<CoreHooks & PackHooks> = { | ||
| configuration: { | ||
| /** | ||
| * Example: | ||
| * ```yaml | ||
| * catalog: | ||
| * react: ^18.3.1 | ||
| * lodash: ^4.17.21 | ||
| * ``` | ||
| */ | ||
| catalog: { | ||
| description: `The default catalog of packages`, | ||
| type: SettingsType.MAP, | ||
| valueDefinition: { | ||
| description: `The catalog of packages`, | ||
| type: SettingsType.STRING, | ||
| }, | ||
| }, | ||
| /** | ||
| * Example: | ||
| * ```yaml | ||
| * catalogs: | ||
| * react18: | ||
| * react: ^18.3.1 | ||
| * react-dom: ^18.3.1 | ||
| * react17: | ||
| * react: ^17.0.2 | ||
| * react-dom: ^17.0.2 | ||
| * ``` | ||
| */ | ||
| catalogs: { | ||
| description: `Named catalogs of packages`, | ||
| type: SettingsType.MAP, | ||
| valueDefinition: { | ||
| description: `A named catalog`, | ||
| type: SettingsType.MAP, | ||
| valueDefinition: { | ||
| description: `Package version in the catalog`, | ||
| type: SettingsType.STRING, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| hooks: { | ||
| /** | ||
| * To allow publishing packages with catalog references, we need to replace the | ||
| * catalog references with the actual version ranges during the packing phase. | ||
| */ | ||
| beforeWorkspacePacking: (workspace: Workspace, rawManifest: any) => { | ||
| const project = workspace.project; | ||
|
|
||
| for (const dependencyType of DEPENDENCY_TYPES) { | ||
| const dependencies = rawManifest[dependencyType]; | ||
| if (!dependencies) continue; | ||
|
|
||
| for (const [identStr, range] of Object.entries(dependencies)) { | ||
| if (typeof range !== `string` || !isCatalogReference(range)) continue; | ||
|
|
||
| try { | ||
| // Create a descriptor to resolve from catalog | ||
| const ident = structUtils.parseIdent(identStr); | ||
| const descriptor = structUtils.makeDescriptor(ident, range); | ||
|
|
||
| // Resolve the catalog reference to get the actual version range | ||
| const resolvedDescriptor = resolveDescriptorFromCatalog(project, descriptor); | ||
|
|
||
| // Replace the catalog reference with the resolved range | ||
| dependencies[identStr] = resolvedDescriptor.range; | ||
| } catch { | ||
| // If resolution fails, leave the catalog reference as-is | ||
| // This will allow the error to be caught during normal resolution | ||
| continue; | ||
|
||
| } | ||
| } | ||
| } | ||
| }, | ||
|
|
||
| /** | ||
| * On this hook, we will check if the dependency is a catalog reference, and if so, | ||
| * we will replace the range with the actual range defined in the catalog. | ||
| */ | ||
| reduceDependency: async (dependency: Descriptor, project: Project, locator: Locator, initialDependency: Descriptor, {resolver, resolveOptions}: {resolver: Resolver, resolveOptions: ResolveOptions}) => { | ||
| if (isCatalogReference(dependency.range)) { | ||
| const resolvedDescriptor = resolveDescriptorFromCatalog(project, dependency); | ||
| return resolvedDescriptor; | ||
| } | ||
| return dependency; | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| // eslint-disable-next-line arca/no-default-export | ||
| export default plugin; | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be updated after other discussions are resolved.