Skip to content

Commit

Permalink
Relax peer dependency constraints (#3881)
Browse files Browse the repository at this point in the history
When we release a new version of a controller, our Yarn constraints
enforce that we must also bump any peer dependencies that rely on that
controller to match the new version. And one of our policies is that if
we bump a peer dependency in a package, we must release a new major
version of that package. As a result, because we make changes to
controllers all the time, we also end up making major releases all the
time too. This is unnecessary if most of those changes aren't actually
breaking.

To reduce the number of major releases, this commit relaxes constraints
on peer dependencies in two ways:

1. Peer dependencies for the same package across the monorepo are no
longer required to use the same exact version.
2. A peer dependency on a controller is only required to match the major
version of its corresponding non-peer dependency (which is itself
required to match the current version of that dependency).
 
For instance, if the current version of `@metamask/keyring-controller`
is 12.2.0, a controller would be allowed to declare a peer dependency on
`^12.0.0`. In addition, two controllers would be allowed to use slightly
different versions, as long as they were major-compatible with the
package (for instance, one controller could use `^12.0.0` and another
could use `^12.1.0`).
  • Loading branch information
mcmire authored Feb 6, 2024
1 parent 959b920 commit d3861d0
Showing 1 changed file with 63 additions and 26 deletions.
89 changes: 63 additions & 26 deletions constraints.pro
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ repo_name(RepoUrl, RepoName) :-
RepoNameLength is End - Start,
sub_atom(RepoUrl, PrefixLength, RepoNameLength, SuffixLength, RepoName).

% True if DependencyIdent starts with '@metamask' and ends with '-controller'
% True if DependencyIdent starts with '@metamask' and ends with '-controller'.
is_controller(DependencyIdent) :-
Prefix = '@metamask/',
atom_length(Prefix, PrefixLength),
Expand Down Expand Up @@ -308,43 +308,80 @@ gen_enforced_dependency(WorkspaceCwd, DependencyIdent, 'a range optionally start
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType),
\+ is_valid_version_range(DependencyRange).

% All references to a workspace package must be up to date with the current
% version of that package.
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, CorrectDependencyRange, DependencyType) :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType),
workspace_ident(OtherWorkspaceCwd, DependencyIdent),
workspace_version(OtherWorkspaceCwd, OtherWorkspaceVersion),
atomic_list_concat(['^', OtherWorkspaceVersion], CorrectDependencyRange).

% All dependency ranges for a package must be synchronized across the monorepo
% (the least version range wins), regardless of which "*dependencies" field
% where the package appears.
% All version ranges used to reference one workspace package in another
% workspace package's `dependencies` or `devDependencies` must be the same.
% Among all references to the same dependency across the monorepo, the one with
% the smallest version range will win. (We handle `peerDependencies` in another
% constraint, as it has slightly different logic.)
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, OtherDependencyRange, DependencyType) :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType),
workspace_has_dependency(OtherWorkspaceCwd, DependencyIdent, OtherDependencyRange, OtherDependencyType),
WorkspaceCwd \= OtherWorkspaceCwd,
DependencyRange \= OtherDependencyRange,
npm_version_range_out_of_sync(DependencyRange, OtherDependencyRange).
npm_version_range_out_of_sync(DependencyRange, OtherDependencyRange),
DependencyType \= 'peerDependencies',
OtherDependencyType \= 'peerDependencies'.

% If a dependency is listed under "dependencies", it should not be listed under
% "devDependencies". We match on the same dependency range so that if a
% dependency is listed under both lists, their versions are synchronized and
% then this constraint will apply and remove the "right" duplicate.
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, null, DependencyType) :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, 'dependencies'),
% All version ranges used to reference one workspace package in another
% workspace package's `dependencies` or `devDependencies` must match the current
% version of that package. (We handle `peerDependencies` in another rule.)
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, CorrectDependencyRange, DependencyType) :-
DependencyType \= 'peerDependencies',
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType),
DependencyType == 'devDependencies'.
workspace_ident(OtherWorkspaceCwd, DependencyIdent),
workspace_version(OtherWorkspaceCwd, OtherWorkspaceVersion),
atomic_list_concat(['^', OtherWorkspaceVersion], CorrectDependencyRange).

% If a controller dependency (other than `base-controller`, `eth-keyring-controller` and
% `polling-controller`) is listed under "dependencies", it should also be
% listed under "peerDependencies". Each controller is a singleton, so we need
% to ensure the versions used match expectations.
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, 'peerDependencies') :-
% If a workspace package is listed under another workspace package's
% `dependencies`, it should not also be listed under its `devDependencies`.
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, null, 'devDependencies') :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, 'dependencies').

% Each controller is a singleton, so we need to ensure the versions
% used match expectations. To accomplish this, if a controller (other than
% `base-controller`, `eth-keyring-controller` and `polling-controller`) is
% listed under a workspace package's `dependencies`, it should also be listed
% under its `peerDependencies`, and the major version of the peer dependency
% should match the major part of the current version dependency, with the minor
% and patch parts set to 0. If it is already listed there, then the major
% version should match the current version of the package and the minor and
% patch parts should be <= the corresponding parts.
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, CorrectPeerDependencyRange, 'peerDependencies') :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, 'dependencies'),
\+ workspace_has_dependency(WorkspaceCwd, DependencyIdent, _, 'peerDependencies'),
is_controller(DependencyIdent),
DependencyIdent \= '@metamask/base-controller',
DependencyIdent \= '@metamask/eth-keyring-controller',
DependencyIdent \= '@metamask/polling-controller',
is_controller(DependencyIdent).
workspace_ident(DependencyWorkspaceCwd, DependencyIdent),
workspace_version(DependencyWorkspaceCwd, CurrentDependencyWorkspaceVersion),
parse_version_range(CurrentDependencyWorkspaceVersion, _, CurrentDependencyVersionMajor, _, _),
atomic_list_concat([CurrentDependencyVersionMajor, 0, 0], '.', CorrectPeerDependencyVersion),
atom_concat('^', CorrectPeerDependencyVersion, CorrectPeerDependencyRange).
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, CorrectPeerDependencyRange, 'peerDependencies') :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, SpecifiedPeerDependencyRange, 'peerDependencies'),
is_controller(DependencyIdent),
DependencyIdent \= '@metamask/base-controller',
DependencyIdent \= '@metamask/eth-keyring-controller',
DependencyIdent \= '@metamask/polling-controller',
workspace_ident(DependencyWorkspaceCwd, DependencyIdent),
workspace_version(DependencyWorkspaceCwd, CurrentDependencyVersion),
parse_version_range(CurrentDependencyVersion, _, CurrentDependencyVersionMajor, CurrentDependencyVersionMinor, CurrentDependencyVersionPatch),
parse_version_range(SpecifiedPeerDependencyRange, _, SpecifiedPeerDependencyVersionMajor, SpecifiedPeerDependencyVersionMinor, SpecifiedPeerDependencyVersionPatch),
(
(
SpecifiedPeerDependencyVersionMajor == CurrentDependencyVersionMajor,
(
SpecifiedPeerDependencyVersionMinor @< CurrentDependencyVersionMinor ;
(
SpecifiedPeerDependencyVersionMinor == CurrentDependencyVersionMinor,
SpecifiedPeerDependencyVersionPatch @=< CurrentDependencyVersionPatch
)
)
) ->
CorrectPeerDependencyRange = SpecifiedPeerDependencyRange ;
atom_concat('^', CurrentDependencyVersion, CorrectPeerDependencyRange)
).

% All packages must specify a minimum Node version of 16.
gen_enforced_field(WorkspaceCwd, 'engines.node', '>=16.0.0').
Expand Down

0 comments on commit d3861d0

Please sign in to comment.