From ead5a4dfa37ca020b571c6cc955fe2d6ffaef8ef Mon Sep 17 00:00:00 2001 From: Chance Strickland Date: Thu, 1 Dec 2022 10:55:44 -0800 Subject: [PATCH 1/4] Add script to bump peer deps with changesets version --- .changeset/config.json | 5 +- package.json | 2 +- packages/remix-dev/package.json | 2 +- scripts/bump-peer-dep-ranges.mjs | 79 ++++++++++++++++++++++++++++++++ scripts/tsconfig.json | 1 + 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 scripts/bump-peer-dep-ranges.mjs diff --git a/.changeset/config.json b/.changeset/config.json index d25053e6606..d859940796e 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -26,5 +26,8 @@ "access": "public", "baseBranch": "dev", "updateInternalDependencies": "patch", - "ignore": [] + "ignore": [], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } } diff --git a/package.json b/package.json index f7dc676e39b..716a1e7fdc3 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "posttest:integration": "node ./integration/helpers/cleanup.mjs", "test:primary": "jest", "changeset": "changeset", - "changeset:version": "changeset version", + "changeset:version": "changeset version && node ./scripts/bump-peer-dep-ranges.mjs", "changeset:release": "yarn build --tsc --publish && changeset publish", "version": "node ./scripts/version.js", "version:experimental": "node ./scripts/version.js experimental", diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index 82e321d7817..0846ff0e2a1 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -82,7 +82,7 @@ "type-fest": "^2.16.0" }, "peerDependencies": { - "@remix-run/serve": "1.8.0" + "@remix-run/serve": "^1.8.0" }, "peerDependenciesMeta": { "@remix-run/serve": { diff --git a/scripts/bump-peer-dep-ranges.mjs b/scripts/bump-peer-dep-ranges.mjs new file mode 100644 index 00000000000..09ea355305a --- /dev/null +++ b/scripts/bump-peer-dep-ranges.mjs @@ -0,0 +1,79 @@ +import { spawnSync } from "node:child_process"; +import * as fs from "node:fs"; +import path from "node:path"; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); +const { getPackagesSync } = require("@manypkg/get-packages"); +const gitStatusResult = spawnSync("git", ["status", "--porcelain"]); + +if (gitStatusResult.status !== 0) { + process.exit(gitStatusResult.status || undefined); +} + +const rootDir = path.join(__dirname, ".."); + +const allPackages = getPackagesSync(rootDir).packages; +const allPackageNames = allPackages.map((pkg) => pkg.packageJson.name); + +const pkgChanges = new Map( + gitStatusResult.stdout + .toString() + .trim() + .split("\n") + .filter((line) => /^\s*M\s+.*\/package.json/.test(line)) + .map((line) => { + /** + * @type {string} + * This will always be defined but TS doesn't know that + * @ts-expect-error */ + let gitPath = line.match(/[^\s]+package.json/)[0]; + let fsPath = path.join(rootDir, gitPath); + let packageJson = require(fsPath); + let previousPackageJsonResult = spawnSync("git", [ + "show", + `HEAD:${gitPath}`, + ]); + + if (previousPackageJsonResult.status !== 0) { + process.exit(gitStatusResult.status || undefined); + } + + return [ + packageJson.name, + { + path: fsPath, + packageJson: packageJson, + versionChanged: + packageJson.version !== + JSON.parse(previousPackageJsonResult.stdout.toString().trim()) + .version, + }, + ]; + }) +); + +for (let peerPkg of allPackageNames) { + let peerPkgChange = pkgChanges.get(peerPkg); + if (!peerPkgChange || !peerPkgChange.versionChanged) { + continue; + } + + for (let dependentPkg of allPackages) { + let peerDeps = dependentPkg.packageJson.peerDependencies; + if (!peerDeps || !peerDeps[peerPkg]) { + continue; + } + let pkgJsonCopy = { ...dependentPkg.packageJson }; + // TS not smart enough to realize we checked this before copying the object + // @ts-expect-error + pkgJsonCopy.peerDependencies[ + peerPkg + ] = `^${peerPkgChange.packageJson.version}`; + + fs.writeFileSync( + path.join(dependentPkg.dir, "package.json"), + JSON.stringify(pkgJsonCopy, null, 2) + "\n" + ); + } +} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 03bbed92362..211eeb2764d 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -8,6 +8,7 @@ "forceConsistentCasingInFileNames": true, "allowSyntheticDefaultImports": true, "moduleResolution": "Node", + "module": "ESNext", "noEmit": true, "strict": true, "target": "ES2018" From 11c83a1212bebba735c867dac0a597e4678741a1 Mon Sep 17 00:00:00 2001 From: Chance Strickland Date: Thu, 1 Dec 2022 11:12:21 -0800 Subject: [PATCH 2/4] add comments to bump-peer-dep-ranges.mjs --- scripts/bump-peer-dep-ranges.mjs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripts/bump-peer-dep-ranges.mjs b/scripts/bump-peer-dep-ranges.mjs index 09ea355305a..c5586d5b2f6 100644 --- a/scripts/bump-peer-dep-ranges.mjs +++ b/scripts/bump-peer-dep-ranges.mjs @@ -1,3 +1,29 @@ +// Consider this scenario: +// 1. Run `yarn changeset:version` to bump versions +// 2. Changesets sees that a package has a minor change +// 3. Because we release packages in lockstep, all packages get a minor update +// 4. `@remix-run/dev` has "peerDependencies": { "@remix-run/serve": "1.8.0" } +// 5. This dependency will now be out of range after the update +// 6. Changesets makes the safe bet and updates `@remix-run/dev` to 2.0.0 +// because it can't be sure this doesn't result in a breaking change +// 7. Because we release packages in lockstep, all packages get a major update +// +// In practice, this means any `minor` changeset will result in a major bump, +// which definitely isn't what we want. +// +// Instead, we relaxe the peer dependency range for internal packages. That way +// the update doesn't result in an out-of-range peer dependency, and all +// packages are bumped to the next minor release instead. +// +// Because changesets doesn't automatically bump peer dependencies with the +// relaxed range (which makes sense in most cases), this script does that for +// us. This makes the change safer because updating the leading dependency will +// result in a peer dependency warning if the user doesn't bump the peer +// dependency for some reason. +// +// Thanks to Mateusz Burzyński for the original script +// Copyright (c) 2015 David Khourshid, MIT License +// https://github.com/statelyai/xstate/blob/fb4786b80786d8ff3d44dfa818097b219dab623c/scripts/bump-peer-dep-ranges.js import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import path from "node:path"; From 67ea5e09c07294b86d2ceedd7af0de8f2721891a Mon Sep 17 00:00:00 2001 From: Chance Strickland Date: Thu, 1 Dec 2022 12:07:14 -0800 Subject: [PATCH 3/4] tweak comment --- scripts/bump-peer-dep-ranges.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bump-peer-dep-ranges.mjs b/scripts/bump-peer-dep-ranges.mjs index c5586d5b2f6..f2fbeecf636 100644 --- a/scripts/bump-peer-dep-ranges.mjs +++ b/scripts/bump-peer-dep-ranges.mjs @@ -16,7 +16,7 @@ // packages are bumped to the next minor release instead. // // Because changesets doesn't automatically bump peer dependencies with the -// relaxed range (which makes sense in most cases), this script does that for +// relaxed range (which makes sense in some cases), this script does that for // us. This makes the change safer because updating the leading dependency will // result in a peer dependency warning if the user doesn't bump the peer // dependency for some reason. From 694b7ad566f95d08a65e2852e53bcea85dbb5125 Mon Sep 17 00:00:00 2001 From: Chance Strickland Date: Thu, 1 Dec 2022 14:17:22 -0800 Subject: [PATCH 4/4] Update scripts/bump-peer-dep-ranges.mjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz Burzyński --- scripts/bump-peer-dep-ranges.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/bump-peer-dep-ranges.mjs b/scripts/bump-peer-dep-ranges.mjs index f2fbeecf636..456226e25ed 100644 --- a/scripts/bump-peer-dep-ranges.mjs +++ b/scripts/bump-peer-dep-ranges.mjs @@ -27,16 +27,16 @@ import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import path from "node:path"; -import { createRequire } from "node:module"; +import * as url from "node:url"; +import { getPackagesSync } from "@manypkg/get-packages"; -const require = createRequire(import.meta.url); -const { getPackagesSync } = require("@manypkg/get-packages"); const gitStatusResult = spawnSync("git", ["status", "--porcelain"]); if (gitStatusResult.status !== 0) { process.exit(gitStatusResult.status || undefined); } +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const rootDir = path.join(__dirname, ".."); const allPackages = getPackagesSync(rootDir).packages;