From 95613a32a0ebea97b68a1ac009cc9f31c8e6c5b3 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 16 Dec 2022 13:53:26 -0700 Subject: [PATCH 1/4] Update Yarn constraints to remove ref to proj name When creating the monorepo template, it was useful to revise the Yarn constraints file so that the project/repo name could be changed without having to change a bunch of references in the file. This commit brings over those revisions: * The repository URL is now used as a basis for the homepage URL and bugs URL, so it must be set appropriately (and if it is not, the user will see a suggestion in the correct format). * Additionally, the comments above each constraint have been reworded to be more consistent and understandable, and comments have been added where missing. * Finally, some pairs of constraints which test one thing for private packages and the opposite for public packages have been updated to apply to the root package vs. non-root packages where it made sense. --- constraints.pro | 148 +++++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 51 deletions(-) diff --git a/constraints.pro b/constraints.pro index ef2a0370a8..99aa56c569 100644 --- a/constraints.pro +++ b/constraints.pro @@ -1,6 +1,6 @@ -%%%%% +%=============================================================================== % Utility predicates -%%%%% +%=============================================================================== % True if and only if VersionRange is a value that we would expect to see % following a package in a "*dependencies" field within a `package.json`. @@ -101,24 +101,42 @@ workspace_package_name(WorkspaceCwd, WorkspacePackageName) :- workspace_basename(WorkspaceCwd, WorkspaceBasename), atom_concat('@metamask/', WorkspaceBasename, WorkspacePackageName). -%%%%% +% True if RepoName can be unified with the repository name part of RepoUrl, a +% complete URL for a repository on GitHub. This URL must include the ".git" +% extension. +repo_name(RepoUrl, RepoName) :- + Prefix = 'https://github.com/MetaMask/', + atom_length(Prefix, PrefixLength), + Suffix = '.git', + atom_length(Suffix, SuffixLength), + atom_length(RepoUrl, RepoUrlLength), + sub_atom(RepoUrl, 0, PrefixLength, After, Prefix), + sub_atom(RepoUrl, Before, SuffixLength, 0, Suffix), + Start is RepoUrlLength - After + 1, + End is Before + 1, + RepoNameLength is End - Start, + sub_atom(RepoUrl, PrefixLength, RepoNameLength, SuffixLength, RepoName). + +%=============================================================================== % Constraints -%%%%% +%=============================================================================== -% "name" is required for all workspaces (including the root). +% All packages, published or otherwise, must have a name. \+ gen_enforced_field(WorkspaceCwd, 'name', null). -% The name of the root package can be anything, but the name of a workspace +% The name of the root package can be anything, but the name of a non-root % package must match its directory (e.g., a package located in "packages/foo" % must be called "@metamask/foo"). +% +% NOTE: This assumes that the set of non-root workspaces is flat. Nested +% workspaces will be added in a future change. gen_enforced_field(WorkspaceCwd, 'name', WorkspacePackageName) :- - \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.', workspace_package_name(WorkspaceCwd, WorkspacePackageName). -% "description" is required for all packages. +% All packages, published or otherwise, must have a description. \+ gen_enforced_field(WorkspaceCwd, 'description', null). - -% The value of "description" cannot end with a period. +% The description cannot end with a period. gen_enforced_field(WorkspaceCwd, 'description', DescriptionWithoutTrailingPeriod) :- workspace_field(WorkspaceCwd, 'description', Description), atom_length(Description, Length), @@ -131,91 +149,119 @@ gen_enforced_field(WorkspaceCwd, 'description', DescriptionWithoutTrailingPeriod DescriptionWithoutTrailingPeriod = Description ). -% "keywords" must be the same across all workspace packages -% (and must be unset for the root). +% All published packages must have the same set of NPM keywords. gen_enforced_field(WorkspaceCwd, 'keywords', ['MetaMask', 'Ethereum']) :- \+ workspace_field(WorkspaceCwd, 'private', true). +% Non-published packages do not have any NPM keywords. gen_enforced_field(WorkspaceCwd, 'keywords', null) :- workspace_field(WorkspaceCwd, 'private', true). -% "homepage" must match the name of the package (based on the workspace -% directory name) across all workspace packages (and must be unset for the -% root). +% The homepage of a published package must match its name (which is in turn +% based on its workspace directory name). gen_enforced_field(WorkspaceCwd, 'homepage', CorrectHomepageUrl) :- \+ workspace_field(WorkspaceCwd, 'private', true), workspace_basename(WorkspaceCwd, WorkspaceBasename), - atomic_list_concat(['https://github.com/MetaMask/controllers/tree/main/packages/', WorkspaceBasename, '#readme'], CorrectHomepageUrl). + workspace_field(WorkspaceCwd, 'repository.url', RepoUrl), + repo_name(RepoUrl, RepoName), + atomic_list_concat(['https://github.com/MetaMask/', RepoName, '/tree/main/packages/', WorkspaceBasename, '#readme'], CorrectHomepageUrl). +% Non-published packages do not have a homepage. gen_enforced_field(WorkspaceCwd, 'homepage', null) :- workspace_field(WorkspaceCwd, 'private', true). -% "repository.type" must be "git" for all packages. -gen_enforced_field(WorkspaceCwd, 'repository.type', 'git'). +% The bugs URL of a published package must point to the Issues page for the +% repository. +gen_enforced_field(WorkspaceCwd, 'bugs.url', CorrectBugsUrl) :- + \+ workspace_field(WorkspaceCwd, 'private', true), + workspace_field(WorkspaceCwd, 'repository.url', RepoUrl), + repo_name(RepoUrl, RepoName), + atomic_list_concat(['https://github.com/MetaMask/', RepoName, '/issues'], CorrectBugsUrl). +% Non-published packages do not have a bugs URL. +gen_enforced_field(WorkspaceCwd, 'homepage', null) :- + workspace_field(WorkspaceCwd, 'private', true). -% "repository.url" must be "https://github.com/MetaMask/controllers.git" for all -% packages. -gen_enforced_field(WorkspaceCwd, 'repository.url', 'https://github.com/MetaMask/controllers.git'). +% All packages must specify Git as the repository type. +gen_enforced_field(WorkspaceCwd, 'repository.type', 'git'). -% "license" must be "MIT" for all workspace packages and unset for the root. +% All packages must match the URL of a repo within the MetaMask organization. +gen_enforced_field(WorkspaceCwd, 'repository.url', 'https://github.com/MetaMask/.git') :- + workspace_field(WorkspaceCwd, 'repository.url', RepoUrl), + \+ repo_name(RepoUrl, _). +% The repository URL for non-root packages must match the same URL used for the +% root package. +gen_enforced_field(WorkspaceCwd, 'repository.url', RepoUrl) :- + workspace_field('.', 'repository.url', RepoUrl), + WorkspaceCwd \= '.'. + +% The license for all published packages must be MIT. gen_enforced_field(WorkspaceCwd, 'license', 'MIT') :- \+ workspace_field(WorkspaceCwd, 'private', true). +% Non-published packages do not have a license. gen_enforced_field(WorkspaceCwd, 'license', null) :- workspace_field(WorkspaceCwd, 'private', true). -% "main" must be "dist/index.js" for workspace packages and unset for the -% root. +% The entrypoint for all published packages must be the same. gen_enforced_field(WorkspaceCwd, 'main', './dist/index.js') :- \+ workspace_field(WorkspaceCwd, 'private', true). +% Non-published packages must not specify an entrypoint. gen_enforced_field(WorkspaceCwd, 'main', null) :- workspace_field(WorkspaceCwd, 'private', true). -% "types" must be "dist/index.d.ts" for workspace packages and unset for the -% root. +% The type definitions entrypoint for all publishable packages must be the same. gen_enforced_field(WorkspaceCwd, 'types', './dist/index.d.ts') :- \+ workspace_field(WorkspaceCwd, 'private', true). +% Non-published packages must not specify a type definitions entrypoint. gen_enforced_field(WorkspaceCwd, 'types', null) :- workspace_field(WorkspaceCwd, 'private', true). -% "files" must be ["dist/"] for workspace packages and [] for the root. +% The list of files included in published packages must be everything in `dist/`. gen_enforced_field(WorkspaceCwd, 'files', ['dist/']) :- \+ workspace_field(WorkspaceCwd, 'private', true). +% The root package must specify an empty set of published files. (This is +% required in order to be able to import anything in development-only scripts, +% as otherwise the `node/no-unpublished-require` ESLint rule will disallow it.) gen_enforced_field(WorkspaceCwd, 'files', []) :- - workspace_field(WorkspaceCwd, 'private', true). + WorkspaceCwd = '.'. +% All other non-published packages must not specify a set of published files. +gen_enforced_field(WorkspaceCwd, 'files', null) :- + workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.'. -% All workspace packages must have the same "build:docs" script. +% All non-root packages must have the same "build:docs" script. gen_enforced_field(WorkspaceCwd, 'scripts.build:docs', 'typedoc') :- - \+ workspace_field(WorkspaceCwd, 'private', true). + WorkspaceCwd \= '.'. -% All workspace packages must have the same "prepare-manifest:preview" script. +% All published packages must have the same "prepare-manifest:preview" script. gen_enforced_field(WorkspaceCwd, 'scripts.prepare-manifest:preview', '../../scripts/prepare-preview-manifest.sh') :- \+ workspace_field(WorkspaceCwd, 'private', true). -% All workspace packages must have the same "publish:preview" script. +% All published packages must have the same "publish:preview" script. gen_enforced_field(WorkspaceCwd, 'scripts.publish:preview', 'yarn npm publish --tag preview') :- \+ workspace_field(WorkspaceCwd, 'private', true). -% The "changelog:validate" script for each package must follow a specific -% format. +% The "changelog:validate" script for each published package must run a common +% script with the name of the package as an argument. gen_enforced_field(WorkspaceCwd, 'scripts.changelog:validate', ProperChangelogValidationScript) :- \+ workspace_field(WorkspaceCwd, 'private', true), workspace_package_name(WorkspaceCwd, WorkspacePackageName), atomic_list_concat(['../../scripts/validate-changelog.sh ', WorkspacePackageName], ProperChangelogValidationScript). -% All workspace packages must have the same "test" script. +% All non-root packages must have the same "test" script. gen_enforced_field(WorkspaceCwd, 'scripts.test', 'jest') :- - \+ workspace_field(WorkspaceCwd, 'private', true). + WorkspaceCwd \= '.'. -% All workspace packages must have the same "test:watch" script. +% All non-root packages must have the same "test:watch" script. gen_enforced_field(WorkspaceCwd, 'scripts.test:watch', 'jest --watch') :- - \+ workspace_field(WorkspaceCwd, 'private', true). + WorkspaceCwd \= '.'. -% All dependency ranges must be recognizable. +% All dependency ranges must be recognizable (this makes it possible to apply +% the next two rules effectively). gen_enforced_dependency(WorkspaceCwd, DependencyIdent, 'a range optionally starting with ^ or ~', DependencyType) :- workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), \+ is_valid_version_range(DependencyRange). % All dependency ranges for a package must be synchronized across the monorepo -% (the least version range wins), regardless of which "*dependencies" the -% package appears. +% (the least version range wins), regardless of which "*dependencies" field +% where the package appears. gen_enforced_dependency(WorkspaceCwd, DependencyIdent, OtherDependencyRange, DependencyType) :- workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), workspace_has_dependency(OtherWorkspaceCwd, DependencyIdent, OtherDependencyRange, OtherDependencyType), @@ -232,24 +278,24 @@ gen_enforced_dependency(WorkspaceCwd, DependencyIdent, null, DependencyType) :- workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), DependencyType == 'devDependencies'. -% eth-query has an unlisted dependency on babel-runtime, so that package needs -% to be present if eth-query is present. -gen_enforced_dependency(WorkspaceCwd, 'babel-runtime', '^6.26.0', DependencyType) :- - workspace_has_dependency(WorkspaceCwd, 'eth-query', _, DependencyType). - -% "engines.node" must be ">=14.0.0" for all packages. +% All packages must specify a minimum Node version of 14. gen_enforced_field(WorkspaceCwd, 'engines.node', '>=14.0.0'). -% "publishConfig.access" must be "public" for workspace packages and unset -% for the root. +% All published packages are public. gen_enforced_field(WorkspaceCwd, 'publishConfig.access', 'public') :- \+ workspace_field(WorkspaceCwd, 'private', true). +% Non-published packages do not need to specify their published access level. gen_enforced_field(WorkspaceCwd, 'publishConfig.access', null) :- workspace_field(WorkspaceCwd, 'private', true). -% "publishConfig.registry" must be "https://registry.npmjs.org" for all -% workspace packages and unset for the root. +% All published packages are available on the NPM registry. gen_enforced_field(WorkspaceCwd, 'publishConfig.registry', 'https://registry.npmjs.org/') :- \+ workspace_field(WorkspaceCwd, 'private', true). +% Non-published packages do not need to specify an NPM registry. gen_enforced_field(WorkspaceCwd, 'publishConfig.registry', null) :- workspace_field(WorkspaceCwd, 'private', true). + +% eth-query has an unlisted dependency on babel-runtime, so that package needs +% to be present if eth-query is present. +gen_enforced_dependency(WorkspaceCwd, 'babel-runtime', '^6.26.0', DependencyType) :- + workspace_has_dependency(WorkspaceCwd, 'eth-query', _, DependencyType). From 169622ba8cd26a2a863ccf71578703fd0af9725e Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 19 Dec 2022 09:47:16 -0700 Subject: [PATCH 2/4] Require that bugs and publishConfig are absent on private packages --- constraints.pro | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/constraints.pro b/constraints.pro index 2aa7ebcb45..ddb74b5c44 100644 --- a/constraints.pro +++ b/constraints.pro @@ -175,8 +175,8 @@ gen_enforced_field(WorkspaceCwd, 'bugs.url', CorrectBugsUrl) :- workspace_field(WorkspaceCwd, 'repository.url', RepoUrl), repo_name(RepoUrl, RepoName), atomic_list_concat(['https://github.com/MetaMask/', RepoName, '/issues'], CorrectBugsUrl). -% Non-published packages do not have a bugs URL. -gen_enforced_field(WorkspaceCwd, 'bugs.url', null) :- +% Non-published packages must not have a bugs section. +gen_enforced_field(WorkspaceCwd, 'bugs', null) :- workspace_field(WorkspaceCwd, 'private', true). % All packages must specify Git as the repository type. @@ -284,15 +284,12 @@ gen_enforced_field(WorkspaceCwd, 'engines.node', '>=14.0.0'). % All published packages are public. gen_enforced_field(WorkspaceCwd, 'publishConfig.access', 'public') :- \+ workspace_field(WorkspaceCwd, 'private', true). -% Non-published packages do not need to specify their published access level. -gen_enforced_field(WorkspaceCwd, 'publishConfig.access', null) :- - workspace_field(WorkspaceCwd, 'private', true). - % All published packages are available on the NPM registry. gen_enforced_field(WorkspaceCwd, 'publishConfig.registry', 'https://registry.npmjs.org/') :- \+ workspace_field(WorkspaceCwd, 'private', true). -% Non-published packages do not need to specify an NPM registry. -gen_enforced_field(WorkspaceCwd, 'publishConfig.registry', null) :- +% Non-published packages do not need to specify any publishing settings +% whatsoever. +gen_enforced_field(WorkspaceCwd, 'publishConfig', null) :- workspace_field(WorkspaceCwd, 'private', true). % eth-query has an unlisted dependency on babel-runtime, so that package needs From dab1717430904bb30f1c99e61d1a8e2e6d32c031 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 19 Dec 2022 09:48:23 -0700 Subject: [PATCH 3/4] Remove restriction on files for private packages --- constraints.pro | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/constraints.pro b/constraints.pro index ddb74b5c44..eeeea31514 100644 --- a/constraints.pro +++ b/constraints.pro @@ -213,7 +213,8 @@ gen_enforced_field(WorkspaceCwd, 'types', './dist/index.d.ts') :- gen_enforced_field(WorkspaceCwd, 'types', null) :- workspace_field(WorkspaceCwd, 'private', true). -% The list of files included in published packages must be everything in `dist/`. +% The list of files included in published packages must only include files +% generated during the build step. gen_enforced_field(WorkspaceCwd, 'files', ['dist/']) :- \+ workspace_field(WorkspaceCwd, 'private', true). % The root package must specify an empty set of published files. (This is @@ -221,10 +222,6 @@ gen_enforced_field(WorkspaceCwd, 'files', ['dist/']) :- % as otherwise the `node/no-unpublished-require` ESLint rule will disallow it.) gen_enforced_field(WorkspaceCwd, 'files', []) :- WorkspaceCwd = '.'. -% All other non-published packages must not specify a set of published files. -gen_enforced_field(WorkspaceCwd, 'files', null) :- - workspace_field(WorkspaceCwd, 'private', true), - WorkspaceCwd \= '.'. % All non-root packages must have the same "build:docs" script. gen_enforced_field(WorkspaceCwd, 'scripts.build:docs', 'typedoc') :- From 8ba120b735e50b55163a320dee3f451d92b41ed0 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 19 Dec 2022 10:20:54 -0700 Subject: [PATCH 4/4] Make the "repo URL in wrong format" message more obvious --- constraints.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/constraints.pro b/constraints.pro index eeeea31514..3db42078be 100644 --- a/constraints.pro +++ b/constraints.pro @@ -190,6 +190,7 @@ gen_enforced_field(WorkspaceCwd, 'repository.url', 'https://github.com/MetaMask/ % root package. gen_enforced_field(WorkspaceCwd, 'repository.url', RepoUrl) :- workspace_field('.', 'repository.url', RepoUrl), + repo_name(RepoUrl, _). WorkspaceCwd \= '.'. % The license for all published packages must be MIT.