Skip to content

Update Changesets and experimental release flow #11442

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

Merged
merged 5 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@
"react-router",
"react-router-dom",
"react-router-dom-v5-compat",
"react-router-native"
"react-router-native",
"@react-router/dev",
"@react-router/express",
"@react-router/node",
"@react-router/serve",
"@react-router/server-runtime",
"@react-router/testing"
]
],
"linked": [],
"access": "public",
"baseBranch": "dev",
"updateInternalDependencies": "patch",
"bumpVersionsWithWorkspaceProtocolOnly": true,
"ignore": []
"ignore": ["integration", "integration-*"],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}
31 changes: 22 additions & 9 deletions .github/workflows/release-experimental.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
name: 🚀 Release (experimental)
name: 🧪 Experimental Release

on:
push:
tags:
- "v0.0.0-experimental*"
workflow_dispatch:
inputs:
branch:
required: true

concurrency: ${{ github.workflow }}-${{ github.ref }}

env:
CI: true

jobs:
release:
name: 🧑‍🔬 Experimental Release
if: |
github.repository == 'remix-run/react-router' &&
contains(github.ref, 'experimental')
experimental:
name: 🧪 Experimental Release
if: github.repository == 'remix-run/react-router'
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
# checkout using a custom token so that we can push later on
token: ${{ secrets.NIGHTLY_PAT }}
fetch-depth: 0

- name: 📦 Setup pnpm
Expand All @@ -34,6 +37,16 @@ jobs:
- name: 📥 Install deps
run: pnpm install --frozen-lockfile

- name: ⤴️ Update version
run: |
git config --local user.email "[email protected]"
git config --local user.name "Remix Run Bot"
SHORT_SHA=$(git rev-parse --short HEAD)
NEXT_VERSION=0.0.0-experimental-${SHORT_SHA}
git checkout -b experimental/${NEXT_VERSION}
pnpm run version ${NEXT_VERSION} --skip-prompt
git push origin --tags

- name: 🏗 Build
run: pnpm build

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
id: changesets
uses: changesets/action@v1
with:
version: pnpm run version
version: pnpm run changeset:version
commit: "chore: Update version for release"
title: "chore: Update version for release"
publish: pnpm run release
Expand Down
16 changes: 9 additions & 7 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ Hotfix releases follow the same process as standard releases above, but the `rel

### Experimental releases

Experimental releases and hot-fixes do not need to be branched off of `dev`. Experimental releases can be branched from anywhere as they are not intended for general use.

- Create a new branch for the release: `git checkout -b release-experimental`
- Make whatever changes you need and commit them: `git add . && git commit "experimental changes!"`
- Update version numbers and create a release tag: `pnpm run version:experimental`
- Push to GitHub: `git push origin --follow-tags`
- The CI workflow should automatically trigger from the experimental tag to publish the release to npm
Experimental releases use a [manually-triggered Github Actions workflow](./.github/workflows/release-experimental.yml) and can be built from any existing branch. to build and publish an experimental release:

- Commit your changes to a branch
- Push the branch to github
- Go to the Github Actions UI for the [release-experimental.yml workflow](https://github.com/remix-run/react-router/actions/workflows/release-experimental.yml)
- Click the `Run workflow` dropdown
- Leave the `Use workflow from` dropdown as `main`
- Enter your feature branch in the `branch` input
- Click the `Run workflow` button
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"posttest:integration": "pnpm clean:integration",
"playwright:integration": "playwright test --config ./integration/playwright.config.ts",
"changeset": "changeset",
"version": "changeset version && node ./scripts/remove-prerelease-changelogs.mjs",
"changeset:version": "changeset version && node ./scripts/remove-prerelease-changelogs.mjs",
"publish": "node scripts/publish.js",
"version:experimental": "node ./scripts/version experimental",
"version": "node ./scripts/version",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it true we only use this for experimental releases now via pnpm run version 0.0.0-experimental-[sha] --skip-prompt?

Copy link
Member Author

@markdalgleish markdalgleish Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, but this update is to match the structure in Remix where it's also used for nightly releases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and it's also referenced in contributing.md.

"watch": "rollup -c -w"
},
"jest": {
Expand Down Expand Up @@ -144,7 +144,8 @@
},
"pnpm": {
"patchedDependencies": {
"@changesets/[email protected]": "patches/@[email protected]"
"@changesets/[email protected]": "patches/@[email protected]",
"@changesets/[email protected]": "patches/@[email protected]"
}
}
}
39 changes: 39 additions & 0 deletions patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
diff --git a/dist/assemble-release-plan.cjs.dev.js b/dist/assemble-release-plan.cjs.dev.js
index e1376ca756d69816f8c79637ee7b45161f092167..d9dc4c17cc90bc31794956a603f0baf6c5fee0d9 100644
--- a/dist/assemble-release-plan.cjs.dev.js
+++ b/dist/assemble-release-plan.cjs.dev.js
@@ -257,7 +257,7 @@ function shouldBumpMajor({
// we check if it is a peerDependency because if it is, our dependent bump type might need to be major.
return depType === "peerDependencies" && nextRelease.type !== "none" && nextRelease.type !== "patch" && ( // 1. If onlyUpdatePeerDependentsWhenOutOfRange set to true, bump major if the version is leaving the range.
// 2. If onlyUpdatePeerDependentsWhenOutOfRange set to false, bump major regardless whether or not the version is leaving the range.
- !onlyUpdatePeerDependentsWhenOutOfRange || !semverSatisfies__default['default'](incrementVersion(nextRelease, preInfo), versionRange)) && ( // bump major only if the dependent doesn't already has a major release.
+ !onlyUpdatePeerDependentsWhenOutOfRange || !semverSatisfies__default['default'](incrementVersion(nextRelease, preInfo), versionRange, { includePrerelease: true })) && ( // bump major only if the dependent doesn't already has a major release.
!releases.has(dependent) || releases.has(dependent) && releases.get(dependent).type !== "major");
}

diff --git a/dist/assemble-release-plan.cjs.prod.js b/dist/assemble-release-plan.cjs.prod.js
index 3a83720644a94cdf6e62fa188a72c51c0384d00e..b3ce3ce688c16cafe92fc16569a7850ea644b904 100644
--- a/dist/assemble-release-plan.cjs.prod.js
+++ b/dist/assemble-release-plan.cjs.prod.js
@@ -130,7 +130,7 @@ function getDependencyVersionRanges(dependentPkgJSON, dependencyRelease) {
}

function shouldBumpMajor({dependent: dependent, depType: depType, versionRange: versionRange, releases: releases, nextRelease: nextRelease, preInfo: preInfo, onlyUpdatePeerDependentsWhenOutOfRange: onlyUpdatePeerDependentsWhenOutOfRange}) {
- return "peerDependencies" === depType && "none" !== nextRelease.type && "patch" !== nextRelease.type && (!onlyUpdatePeerDependentsWhenOutOfRange || !semverSatisfies__default.default(incrementVersion(nextRelease, preInfo), versionRange)) && (!releases.has(dependent) || releases.has(dependent) && "major" !== releases.get(dependent).type);
+ return "peerDependencies" === depType && "none" !== nextRelease.type && "patch" !== nextRelease.type && (!onlyUpdatePeerDependentsWhenOutOfRange || !semverSatisfies__default.default(incrementVersion(nextRelease, preInfo), versionRange, { includePrerelease: true })) && (!releases.has(dependent) || releases.has(dependent) && "major" !== releases.get(dependent).type);
}

function flattenReleases(changesets, packagesByName, ignoredPackages) {
diff --git a/dist/assemble-release-plan.esm.js b/dist/assemble-release-plan.esm.js
index 62891eb5dee97a33e6587514267c3cde5b314830..d183129242ce8582ce2e7a40d507b46f51583427 100644
--- a/dist/assemble-release-plan.esm.js
+++ b/dist/assemble-release-plan.esm.js
@@ -246,7 +246,7 @@ function shouldBumpMajor({
// we check if it is a peerDependency because if it is, our dependent bump type might need to be major.
return depType === "peerDependencies" && nextRelease.type !== "none" && nextRelease.type !== "patch" && ( // 1. If onlyUpdatePeerDependentsWhenOutOfRange set to true, bump major if the version is leaving the range.
// 2. If onlyUpdatePeerDependentsWhenOutOfRange set to false, bump major regardless whether or not the version is leaving the range.
- !onlyUpdatePeerDependentsWhenOutOfRange || !semverSatisfies(incrementVersion(nextRelease, preInfo), versionRange)) && ( // bump major only if the dependent doesn't already has a major release.
+ !onlyUpdatePeerDependentsWhenOutOfRange || !semverSatisfies(incrementVersion(nextRelease, preInfo), versionRange, { includePrerelease: true })) && ( // bump major only if the dependent doesn't already has a major release.
!releases.has(dependent) || releases.has(dependent) && releases.get(dependent).type !== "major");
}

10 changes: 7 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 27 additions & 66 deletions scripts/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,7 @@ function getNextVersion(currentVersion, givenVersion, prereleaseId) {
);
}

let nextVersion;
if (givenVersion === "experimental") {
let hash = execSync(`git rev-parse --short HEAD`).toString().trim();
nextVersion = `0.0.0-experimental-${hash}`;
} else {
// @ts-ignore
nextVersion = semver.inc(currentVersion, givenVersion, prereleaseId);
}

let nextVersion = semver.inc(currentVersion, givenVersion, prereleaseId);
invariant(nextVersion != null, `Invalid version specifier: ${givenVersion}`);

return nextVersion;
Expand All @@ -53,7 +45,7 @@ async function run() {
let args = process.argv.slice(2);
let givenVersion = args[0];
let prereleaseId = args[1];
let isExperimental = givenVersion === "experimental";
let isSnapshotVersion = givenVersion.startsWith("0.0.0-");

// 0. Make sure the working directory is clean
ensureCleanWorkingDirectory();
Expand All @@ -76,59 +68,34 @@ async function run() {

if (answer === false) return 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's true we only use this for experimentals now, we might be able to shed some more stuff in here:

  • This prompt stuff (step 2) isn't necessary since we're using --skip-prompt, so we could remove that CLI flag and this code
  • The manual version bump above (if (version == null)) I think is no longer needed since we'll always provide a version as a CLI arg?
  • I think all the isSnapshotVersion conditionals could go away since it'll only be used for those

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I've simplified the script now so that the version specifier is required.

In the current package structure, the isSnapshotVersion is still required because it's used to detect experimental/nightly releases and ensure that @remix-run/router also gets a version update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's probably a moot point too since the router will be collapsing into react-router too so everything will be versioned identically moving forward. We can tackle that when we remove the router package 👍


// We only handle @remix-run/router for experimental since in normal/pre
// releases it's versioned independently from the rest of the packages
if (isExperimental) {
routerVersion = version;
// 2.5. Update @remix-run/router version
await updatePackageConfig("router", (config) => {
config.version = routerVersion;
// 3. Bump package versions
let packageDirNamesToBump = [
// We only handle @remix-run/router for snapshot versions since in normal/pre
// releases it's versioned independently from the rest of the packages
...(isSnapshotVersion ? ["router"] : []),
"react-router",
"react-router-dom",
"react-router-dom-v5-compat",
"react-router-native",
"remix-dev",
"remix-express",
"remix-node",
"remix-serve",
"remix-server-runtime",
"remix-testing",
];
for (let packageDirName of packageDirNamesToBump) {
let packageName;
await updatePackageConfig(packageDirName, (pkg) => {
packageName = pkg.name;
pkg.version = version;
});
console.log(
chalk.green(` Updated @remix-run/router to version ${version}`)
chalk.green(` Updated ${packageName} to version ${version}`)
);
}

// 3. Update react-router version
await updatePackageConfig("react-router", (config) => {
config.version = version;
if (isExperimental) {
config.dependencies["@remix-run/router"] = routerVersion;
}
});
console.log(chalk.green(` Updated react-router to version ${version}`));

// 4. Update react-router-dom version + react-router dep
await updatePackageConfig("react-router-dom", (config) => {
config.version = version;
if (isExperimental) {
config.dependencies["@remix-run/router"] = routerVersion;
}
config.dependencies["react-router"] = version;
});
console.log(
chalk.green(` Updated react-router-dom to version ${version}`)
);

// 4.1 Update react-router-dom-v5-compat version + react-router dep
await updatePackageConfig("react-router-dom-v5-compat", (config) => {
config.version = version;
config.dependencies["react-router"] = version;
});
console.log(
chalk.green(` Updated react-router-dom-v5-compat to version ${version}`)
);

// 5. Update react-router-native version + react-router dep
await updatePackageConfig("react-router-native", (config) => {
config.version = version;
config.dependencies["react-router"] = version;
});
console.log(
chalk.green(` Updated react-router-native to version ${version}`)
);

// 6. Update react-router and react-router-dom versions in the examples
// 4. Update react-router and react-router-dom versions in the examples
let examples = await fsp.readdir(EXAMPLES_DIR);
for (const example of examples) {
let stat = await fsp.stat(path.join(EXAMPLES_DIR, example));
Expand All @@ -147,18 +114,12 @@ async function run() {
});
}

// 7. Sync up the pnpm-lock.yaml for pnpm if this is an experimental release
if (isExperimental) {
console.log(chalk.green(" Syncing pnpm lockfile..."));
execSync("pnpm install --no-frozen-lockfile");
}

// 8. Commit and tag
// 5. Commit and tag
execSync(`git commit --all --message="Version ${version}"`);
execSync(`git tag -a -m "Version ${version}" v${version}`);
console.log(chalk.green(` Committed and tagged version ${version}`));

if (givenVersion !== "experimental") {
if (!isSnapshotVersion) {
console.log(
chalk.red(
` 🚨 @remix-run/router isn't handled by this script, do it manually!`
Expand Down
Loading