Skip to content

Commit cb6a323

Browse files
chore(yarn): update to use js config for constraints (#11290)
We currently use a prolog file to define the constraints yarn applies when you run `yarn constraints` (which is run with `yarn check`). You can see [here](https://yarnpkg.com/features/constraints) that yarn states this format should be considered deprecated. Instead a `yarn.config.cjs` file is recommended. This change switches our current prolog one to this new js one. I added rules to enforce some consistent and correctly specified fields are present on our package.json files. We can add to this in the future should we wish to.
1 parent 264bbda commit cb6a323

File tree

5 files changed

+193
-28
lines changed

5 files changed

+193
-28
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
uses: ./.github/actions/set-up-job
5353
with:
5454
set-up-yarn-cache: false
55-
yarn-install-directory: ./tasks/check
55+
yarn-install-directory: .
5656
build: false
5757

5858
- name: ✅ Check constraints, dependencies, and package.json's

constraints.pro

-27
This file was deleted.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"@types/prompts": "2.4.9",
8888
"@typescript-eslint/eslint-plugin": "8.1.0",
8989
"@typescript-eslint/parser": "8.1.0",
90+
"@yarnpkg/types": "4.0.0",
9091
"all-contributors-cli": "6.26.1",
9192
"babel-jest": "^29.7.0",
9293
"babel-plugin-auto-import": "1.1.0",

yarn.config.cjs

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/* eslint-env node */
2+
// @ts-check
3+
4+
/**
5+
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Context} Context
6+
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Workspace} Workspace
7+
*/
8+
9+
/** @type {import('@yarnpkg/types')} */
10+
const { defineConfig } = require(`@yarnpkg/types`)
11+
12+
/**
13+
* This rule will enforce that a workspace MUST depend on the same version of a
14+
* dependency as the one used by the other workspaces.
15+
*
16+
* @param {Context} context
17+
*/
18+
function enforceConsistentDependenciesAcrossTheProject({ Yarn }) {
19+
for (const dependency of Yarn.dependencies()) {
20+
if (dependency.type === `peerDependencies`) {
21+
continue
22+
}
23+
24+
for (const otherDependency of Yarn.dependencies({
25+
ident: dependency.ident,
26+
})) {
27+
if (otherDependency.type === `peerDependencies`) {
28+
continue
29+
}
30+
31+
if (
32+
(dependency.type === `devDependencies` ||
33+
otherDependency.type === `devDependencies`) &&
34+
Yarn.workspace({ ident: otherDependency.ident })
35+
) {
36+
continue
37+
}
38+
39+
dependency.update(otherDependency.range)
40+
}
41+
}
42+
}
43+
44+
/**
45+
* This rule will enforce that a workspace MUST depend on the same version of a
46+
* dependency as the one used by the other workspaces.
47+
*
48+
* @param {Context} context
49+
*/
50+
function enforceWorkspaceDependenciesWhenPossible({ Yarn }) {
51+
for (const dependency of Yarn.dependencies()) {
52+
if (!Yarn.workspace({ ident: dependency.ident })) {
53+
continue
54+
}
55+
56+
dependency.update(`workspace:*`)
57+
}
58+
}
59+
60+
/**
61+
* This rule will enforce that a dependency doesn't appear in both `dependencies`
62+
* and `devDependencies`.
63+
*
64+
* @param {Context} context
65+
*/
66+
function enforceNotProdAndDevDependencies({ Yarn }) {
67+
for (const workspace of Yarn.workspaces()) {
68+
const dependencies = Yarn.dependencies({ workspace, type: 'dependencies' })
69+
const devDependencies = Yarn.dependencies({
70+
workspace,
71+
type: 'devDependencies',
72+
})
73+
for (const dependency of dependencies) {
74+
if (
75+
devDependencies.find(
76+
(devDependency) => devDependency.ident === dependency.ident,
77+
)
78+
) {
79+
dependency.error(
80+
`The dependency '${dependency.ident}' should not appear in both dependencies and devDependencies`,
81+
)
82+
}
83+
}
84+
}
85+
}
86+
87+
/**
88+
* This rule will enforce that any package built with babel (identified by the
89+
* presence of a 'build:js' script in its `package.json`) must depend on the
90+
* '@babel/runtime-corejs3' and 'core-js' packages.
91+
*
92+
* @param {Context} context
93+
*/
94+
function enforceBabelDependencies({ Yarn }) {
95+
for (const workspace of Yarn.workspaces()) {
96+
const packageJson = workspace.manifest
97+
if (!packageJson.scripts?.[`build:js`]) {
98+
continue
99+
}
100+
101+
const dependencies = Yarn.dependencies({
102+
workspace,
103+
type: 'dependencies',
104+
})
105+
const requiredDependencies = [`@babel/runtime-corejs3`, `core-js`]
106+
for (const dependency of requiredDependencies) {
107+
if (!dependencies.find((dep) => dep.ident === dependency)) {
108+
workspace.error(
109+
`The package '${workspace.cwd}' must depend on '${dependency}' to build with babel`,
110+
)
111+
}
112+
}
113+
}
114+
}
115+
116+
/**
117+
* This rule will enforce that the specified fields are present in the
118+
* `package.json` of all workspaces.
119+
*
120+
* @param {Context} context
121+
* @param {string[]} fields
122+
*/
123+
function enforceFieldsOnAllWorkspaces({ Yarn }, fields) {
124+
for (const workspace of Yarn.workspaces()) {
125+
// Skip the root workspace
126+
if (workspace.cwd === '.') {
127+
continue
128+
}
129+
130+
for (const field of fields) {
131+
if (!workspace.manifest[field]) {
132+
workspace.error(
133+
`The field '${field}' is required in the package.json of '${workspace.cwd}'`,
134+
)
135+
}
136+
}
137+
}
138+
}
139+
140+
/**
141+
* This rule will enforce that the specified fields are present in the
142+
* `package.json` of all workspaces and that they have the expected value.
143+
*
144+
* @param {Context} context
145+
* @param {Record<string, ((workspace: Workspace) => any) | string>} fields
146+
*/
147+
function enforceFieldsWithValuesOnAllWorkspaces({ Yarn }, fields) {
148+
for (const workspace of Yarn.workspaces()) {
149+
// Skip the root workspace
150+
if (workspace.cwd === '.') {
151+
continue
152+
}
153+
154+
for (const [field, value] of Object.entries(fields)) {
155+
workspace.set(
156+
field,
157+
typeof value === `function` ? value(workspace) : value,
158+
)
159+
}
160+
}
161+
}
162+
163+
module.exports = defineConfig({
164+
constraints: async (ctx) => {
165+
enforceConsistentDependenciesAcrossTheProject(ctx)
166+
enforceWorkspaceDependenciesWhenPossible(ctx)
167+
enforceNotProdAndDevDependencies(ctx)
168+
enforceBabelDependencies(ctx)
169+
enforceFieldsOnAllWorkspaces(ctx, [
170+
'name',
171+
'version',
172+
// 'description', // TODO(jgmw): Add description to all packages and uncomment this line
173+
])
174+
enforceFieldsWithValuesOnAllWorkspaces(ctx, {
175+
license: 'MIT',
176+
['repository.type']: 'git',
177+
['repository.url']: 'git+https://github.com/redwoodjs/redwood.git',
178+
['repository.directory']: (workspace) => workspace.cwd,
179+
})
180+
},
181+
})

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -11648,6 +11648,15 @@ __metadata:
1164811648
languageName: node
1164911649
linkType: hard
1165011650

11651+
"@yarnpkg/types@npm:4.0.0":
11652+
version: 4.0.0
11653+
resolution: "@yarnpkg/types@npm:4.0.0"
11654+
dependencies:
11655+
tslib: "npm:^2.4.0"
11656+
checksum: 10c0/41f67a4aa5c414c1e228f51453451fa15e0dd70c5cf2b1ae1ca142a3f018f25e4a37e60372cd0f5970c755e1804a2e31e208bff427add1cf13f899b0b9adc1e0
11657+
languageName: node
11658+
linkType: hard
11659+
1165111660
"@zkochan/js-yaml@npm:0.0.7":
1165211661
version: 0.0.7
1165311662
resolution: "@zkochan/js-yaml@npm:0.0.7"
@@ -26656,6 +26665,7 @@ __metadata:
2665626665
"@types/prompts": "npm:2.4.9"
2665726666
"@typescript-eslint/eslint-plugin": "npm:8.1.0"
2665826667
"@typescript-eslint/parser": "npm:8.1.0"
26668+
"@yarnpkg/types": "npm:4.0.0"
2665926669
all-contributors-cli: "npm:6.26.1"
2666026670
babel-jest: "npm:^29.7.0"
2666126671
babel-plugin-auto-import: "npm:1.1.0"

0 commit comments

Comments
 (0)