From 8c9c59aed4f9fe2c1bd132d13570e9d63ca5f55c Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 13 Apr 2026 15:23:56 -0400 Subject: [PATCH 01/36] Changes: add PR number or git sha link to changelogs --- scripts/changes/changes.ts | 21 ++++++++++++++++----- scripts/utils/git.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/scripts/changes/changes.ts b/scripts/changes/changes.ts index c6f7b13fd9..986922aa4f 100644 --- a/scripts/changes/changes.ts +++ b/scripts/changes/changes.ts @@ -11,6 +11,7 @@ import { getPackagePath, packageNameToDirectoryName, } from "../utils/packages.ts"; +import { getCommitSubject, getFileSha, parsePrNumber } from "../utils/git.ts"; const bumpTypes = ["major", "minor", "patch", "unstable"] as const; type BumpType = (typeof bumpTypes)[number]; @@ -38,6 +39,8 @@ interface ChangeFile { file: string; bump: BumpType; content: string; + gitSha?: string; + prNumber?: number; } interface ValidationError { @@ -171,7 +174,9 @@ function parsePackageChanges(packageDirName: string): ParsedPackageChanges { } // File is valid, add to changes - changes.push({ file, bump, content }); + let gitSha = getFileSha(filePath).substring(0, 7); + let prNumber = parsePrNumber(getCommitSubject(gitSha)) ?? undefined; + changes.push({ file, bump, content, gitSha, prNumber }); } if (errors.length > 0) { @@ -394,16 +399,22 @@ function hasBreakingChangePrefix(content: string): boolean { /** * Formats a changelog entry from change file content */ -function formatChangelogEntry(content: string): string { - let lines = content.trim().split("\n"); +function formatChangelogEntry(change: ChangeFile): string { + let lines = change.content.trim().split("\n"); + let base = "https://github.com/remix-run/react-router"; + let link = change.prNumber + ? ` ([#${change.prNumber}](${base}/pull/${change.prNumber}))` + : change.gitSha + ? ` ([[${change.gitSha}](${base}/commit/${change.gitSha}))` + : ""; if (lines.length === 1) { - return `- ${lines[0]}`; + return `- ${lines[0]}${link}`; } // Multi-line: first line is bullet, rest are indented let [firstLine, ...restLines] = lines; - let formatted = [`- ${firstLine}`]; + let formatted = [`- ${firstLine}${link}`]; for (let line of restLines) { // Add proper indentation for continuation lines diff --git a/scripts/utils/git.ts b/scripts/utils/git.ts index eec179fa66..2d6bedd767 100644 --- a/scripts/utils/git.ts +++ b/scripts/utils/git.ts @@ -129,3 +129,38 @@ export function getRemoteTagTarget(tag: string): string | null { export function tagExists(tag: string): boolean { return getLocalTagTarget(tag) !== null || getRemoteTagTarget(tag) !== null; } + +/** + * Gets the git SHA of the commit that last modified a file. + * Falls back to HEAD if the file has no git history (e.g., untracked or newly staged). + */ +export function getFileSha(filePath: string): string { + let normalizedPath = filePath.replaceAll("\\", "/"); + try { + let sha = execGit(["log", "-1", "--format=%H", "--", normalizedPath]); + if (sha) return sha; + } catch {} + return execGit(["rev-parse", "HEAD"]); +} + +/** + * Gets the subject line (first line) of a commit message for a given SHA. + */ +export function getCommitSubject(sha: string): string { + return execGit(["log", "-1", "--format=%s", sha]); +} + +/** + * Parses a GitHub PR number from a commit subject line. + * Supports squash merge format "description (#123)" and + * merge commit format "Merge pull request #123 from ...". + */ +export function parsePrNumber(subject: string): number | null { + let squashMatch = subject.match(/\(#(\d+)\)\s*$/); + if (squashMatch) return parseInt(squashMatch[1], 10); + + let mergeMatch = subject.match(/^Merge pull request #(\d+)/i); + if (mergeMatch) return parseInt(mergeMatch[1], 10); + + return null; +} From 5427531aad82acd07ce46411dacba0353d346d75 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 13 Apr 2026 17:11:18 -0400 Subject: [PATCH 02/36] Update release finish script --- scripts/changes/release.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/changes/release.sh b/scripts/changes/release.sh index c845689e67..f98f183c66 100755 --- a/scripts/changes/release.sh +++ b/scripts/changes/release.sh @@ -77,7 +77,12 @@ elif [[ "${COMMAND}" == "finish" ]]; then git push git branch -d release - + git branch -d release-pr &> /dev/null || true + git ls-remote --exit-code --heads origin release-pr + EXIT_CODE=$? + if [[ $EXIT_CODE == '0' ]]; then + git push origin --delete release-pr + fi fi set +e From b34d1e5e66116e46a8f4f7f49dd76f61a619ffd9 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 13 Apr 2026 17:19:16 -0400 Subject: [PATCH 03/36] Fix scripts type error --- scripts/changes/changes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/changes/changes.ts b/scripts/changes/changes.ts index 986922aa4f..0e86206a7b 100644 --- a/scripts/changes/changes.ts +++ b/scripts/changes/changes.ts @@ -475,7 +475,7 @@ function generateBumpTypeSection( ); for (let change of changes) { - lines.push(formatChangelogEntry(change.content)); + lines.push(formatChangelogEntry(change)); if (includeBlankLine) { lines.push(""); } From b88d3f713ec3a76b01b64ae1dfe9568cfee3c253 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 14 Apr 2026 11:49:14 -0400 Subject: [PATCH 04/36] Add PR Preview builds (#14975) --- .github/workflows/preview.yml | 39 +++++++++-- package.json | 1 + scripts/pr-preview.ts | 123 ++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 scripts/pr-preview.ts diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 40d45785d8..1e3c2ce6e1 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -3,6 +3,9 @@ # Commits to `dev` push builds to a `preview/dev` branch: # pnpm install "remix-run/react-router#preview/dev&path:packages/react-router" # +# Pull Requests create `preview/pr-{number}` branches: +# pnpm install "remix-run/react-router#preview/pr-12345&path:packages/react-router" +# # Can also be dispatched manually with base/installable branches to provide # `experimental` branches from PRs or otherwise. @@ -20,6 +23,8 @@ on: installableBranch: description: Installable Branch required: true + pull_request: + types: [opened, synchronize, reopened, closed] concurrency: # Include `event_name` here because when a pull_request is merged (closed), the @@ -30,16 +35,23 @@ concurrency: jobs: preview: - if: github.repository == 'remix-run/react-router' + # Don't run on PRs from forked repos + if: github.repository == 'remix-run/react-router' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) runs-on: ubuntu-latest steps: - name: Checkout (push) if: github.event_name == 'push' - uses: actions/checkout@v4 + uses: actions/checkout@v6 + + - name: Checkout (pull_request) + if: github.event_name == 'pull_request' + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Checkout (workflow_dispatch) if: github.event_name == 'workflow_dispatch' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ inputs.baseBranch }} @@ -47,7 +59,7 @@ jobs: uses: pnpm/action-setup@v4 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: "package.json" cache: pnpm @@ -68,6 +80,17 @@ jobs: git push --force --set-upstream origin preview/dev echo "๐Ÿ’ฟ pushed installable branch: https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)" + # Build and force push over the PR preview/pr-{number} branch + comment on the PR + - name: Build/push branch (pull_request) + if: github.event_name == 'pull_request' && github.event.pull_request.state == 'open' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + pnpm run setup-installable-branch preview/pr-${{ github.event.pull_request.number }} + git push --force --set-upstream origin preview/pr-${{ github.event.pull_request.number }} + echo "pushed installable branch: https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)" + pnpm run pr-preview comment ${{ github.event.pull_request.number }} preview/pr-${{ github.event.pull_request.number }} + # Build and normal push for experimental releases to avoid unintended force # pushes over remote branches in case of a branch name collision - name: Build/push branch (workflow_dispatch) @@ -76,3 +99,11 @@ jobs: pnpm run setup-installable-branch ${{ inputs.installableBranch }} git push --set-upstream origin ${{ inputs.installableBranch }} echo "๐Ÿ’ฟ pushed installable branch: https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)" + + # Cleanup PR preview/pr-{number} branches when the PR is closed + - name: Cleanup preview branch + if: github.event_name == 'pull_request' && github.event.pull_request.state == 'closed' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + pnpm run pr-preview cleanup ${{ github.event.pull_request.number }} preview/pr-${{ github.event.pull_request.number }} diff --git a/package.json b/package.json index 7acd0f368f..5ef7333212 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "format:check": "prettier --ignore-path .prettierignore --check .", "lint": "eslint --cache .", "playground": "node ./scripts/playground.js", + "pr-preview": "node ./scripts/pr-preview.ts", "prerelease": "pnpm build", "setup-installable-branch": "node scripts/setup-installable-branch.ts", "test": "jest", diff --git a/scripts/pr-preview.ts b/scripts/pr-preview.ts new file mode 100644 index 0000000000..7a2b219371 --- /dev/null +++ b/scripts/pr-preview.ts @@ -0,0 +1,123 @@ +/** + * PR Preview Script + * + * This script manages preview builds for pull requests by: + * - Creating comments on PRs with installation instructions for preview builds + * - Cleaning up preview branches when PRs are merged or closed + * + * Commands: + * - `comment `: Adds a comment to the specified PR with instructions + * to install the preview build. Updates any existing preview comments. + * - `cleanup `: Deletes the preview branch from the remote repository + * and adds a cleanup notification comment to the PR. + * + * Usage: `node pr-preview.ts ` + */ + +import { parseArgs } from "node:util"; + +import { + createPrComment, + deletePrComment, + getPrComments, + updatePrComment, +} from "./utils/github.ts"; +import { logAndExec } from "./utils/process.ts"; + +const STICKY_MARKER = ""; +const CLEANUP_MARKER = ""; + +const { positionals } = parseArgs({ + allowPositionals: true, + strict: true, +}); + +if (positionals.length !== 3) { + printUsage(); + process.exit(1); +} + +const [command, prNumberString, branch] = positionals; +const prNumber = parseInt(prNumberString, 10); +if (isNaN(prNumber) || prNumber <= 0) { + printUsage(); + throw new Error(`Invalid PR number: ${prNumberString}`); +} + +const commands: Record Promise> = { + comment, + cleanup, +}; + +if (commands[command]) { + await commands[command](); +} else { + printUsage(); + throw new Error(`Unknown command: ${command}`); +} + +function printUsage() { + console.error("Usage: node pr-preview.ts "); + console.error( + " comment - Add preview comment to PR", + ); + console.error( + " cleanup - Delete branch from origin", + ); +} + +async function comment() { + let commentBody = `\ +${STICKY_MARKER} +### Preview Build Available + +Preview builds have been created for this PR. You can install them using: + +\`\`\`sh +# Install react-router +pnpm install "remix-run/react-router#${branch}&path:packages/react-router" + +# Install other packages as necessary +pnpm install "remix-run/react-router#${branch}&path:packages/react-router-node" +pnpm install "remix-run/react-router#${branch}&path:packages/react-router-serve" +pnpm install "remix-run/react-router#${branch}&path:packages/react-router-dev" +\`\`\` + +These preview builds will be updated automatically as you push new commits.`; + + // Get existing comments + let comments = await getPrComments(prNumber); + + // Only add a comment if one doesn't already exist + let stickyComment = comments.find((comment) => + comment.body?.includes(STICKY_MARKER), + ); + if (stickyComment) { + console.log("Updating preview comment on PR"); + await updatePrComment(stickyComment.id, commentBody); + } else { + console.log("Adding preview comment to PR"); + await createPrComment(prNumber, commentBody); + } + + // Delete cleanup comment if it exists + let cleanupComment = comments.find((comment) => + comment.body?.includes(CLEANUP_MARKER), + ); + if (cleanupComment) { + console.log("Deleting existing cleanup comment"); + await deletePrComment(cleanupComment.id); + } +} + +async function cleanup() { + console.log(`Deleted branch: ${branch}`); + await logAndExec(`git push --delete origin ${branch}`); + + let commentBody = `\ +${CLEANUP_MARKER} +The preview branch \`${branch}\` has been deleted now that this PR is merged/closed.`; + + console.log("Adding cleanup comment to PR"); + await createPrComment(prNumber, commentBody); +} From 46321cf2767cb820c1be8bbea3bafbc32c6c4ffd Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 14 Apr 2026 13:15:53 -0400 Subject: [PATCH 05/36] Remove changesets (#14977) --- .agents/skills/fix-bug/SKILL.md | 12 +- .changeset/README.md | 8 - .changeset/config.json | 32 - .github/workflows/changes-file.yml | 41 + .github/workflows/integration-full.yml | 2 +- .github/workflows/integration-pr-ubuntu.yml | 2 +- AGENTS.md | 9 +- GOVERNANCE.md | 2 +- docs/community/contributing.md | 13 + package.json | 12 +- .../@changesets__assemble-release-plan.patch | 60 -- ...angesets__get-dependents-graph@1.3.6.patch | 47 - pnpm-lock.yaml | 909 +----------------- scripts/changes/add.ts | 138 +++ scripts/changes/check-pr.ts | 81 ++ scripts/find-release-from-changeset.js | 37 - scripts/finish-stable-release.sh | 50 - scripts/package.json | 1 + scripts/remove-prerelease-changelogs.mjs | 123 --- scripts/start-prerelease.sh | 29 - scripts/utils/github.ts | 16 + 21 files changed, 314 insertions(+), 1310 deletions(-) delete mode 100644 .changeset/README.md delete mode 100644 .changeset/config.json create mode 100644 .github/workflows/changes-file.yml delete mode 100644 patches/@changesets__assemble-release-plan.patch delete mode 100644 patches/@changesets__get-dependents-graph@1.3.6.patch create mode 100644 scripts/changes/add.ts create mode 100644 scripts/changes/check-pr.ts delete mode 100644 scripts/find-release-from-changeset.js delete mode 100755 scripts/finish-stable-release.sh delete mode 100644 scripts/remove-prerelease-changelogs.mjs delete mode 100755 scripts/start-prerelease.sh diff --git a/.agents/skills/fix-bug/SKILL.md b/.agents/skills/fix-bug/SKILL.md index a1904c4e08..eaded97045 100644 --- a/.agents/skills/fix-bug/SKILL.md +++ b/.agents/skills/fix-bug/SKILL.md @@ -121,20 +121,16 @@ pnpm lint pnpm typecheck ``` -### 6. Create a Changeset +### 6. Create a Change file -Create `.changeset/.md`: +Create a change file at `packages//.changes/..md`. `` should be either `patch`, `minor`, `major` or `unstable` to indicate the type of API change being made. -```markdown ---- -"react-router": patch ---- +Format: +```markdown fix: ``` -Use `patch` for bug fixes. Only include packages in the frontmatter that were actually changed. - ### 7. Report Results Summarize: diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index e5b6d8d6a6..0000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 19a0281431..0000000000 --- a/.changeset/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", - "changelog": [ - "@remix-run/changelog-github", - { "repo": "remix-run/react-router" } - ], - "commit": false, - "fixed": [ - [ - "create-react-router", - "react-router", - "react-router-dom", - "@react-router/architect", - "@react-router/cloudflare", - "@react-router/dev", - "@react-router/fs-routes", - "@react-router/express", - "@react-router/node", - "@react-router/remix-routes-option-adapter", - "@react-router/serve" - ] - ], - "linked": [], - "access": "public", - "baseBranch": "dev", - "updateInternalDependencies": "patch", - "bumpVersionsWithWorkspaceProtocolOnly": true, - "ignore": ["integration", "integration-*", "@playground/*"], - "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { - "onlyUpdatePeerDependentsWhenOutOfRange": true - } -} diff --git a/.github/workflows/changes-file.yml b/.github/workflows/changes-file.yml new file mode 100644 index 0000000000..12a9df2c62 --- /dev/null +++ b/.github/workflows/changes-file.yml @@ -0,0 +1,41 @@ +name: ๐Ÿ“ Change File Check + +on: + pull_request: + branches: + - dev + types: [opened, synchronize, reopened] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: ๐Ÿ“ Change File Check + # TODO: Temporarily only run on my PRs for testing purposes + if: github.repository == 'remix-run/react-router' && github.event.pull_request.user.login == 'brophdawg11' + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v6 + + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + + - name: โŽ” Setup node + uses: actions/setup-node@v6 + with: + node-version-file: ".nvmrc" + cache: pnpm + + - name: ๐Ÿ“ฅ Install deps + run: pnpm install --frozen-lockfile + + - name: ๐Ÿ“ Check change file and update PR comment + env: + GITHUB_TOKEN: ${{ github.token }} + run: node scripts/changes/check-pr.ts ${{ github.event.pull_request.number }} diff --git a/.github/workflows/integration-full.yml b/.github/workflows/integration-full.yml index 57913eaf61..91633ebe83 100644 --- a/.github/workflows/integration-full.yml +++ b/.github/workflows/integration-full.yml @@ -9,7 +9,7 @@ on: - main - dev paths-ignore: - - ".changeset/**" + - "packages/*/.changes/**" - "decisions/**" - "docs/**" - "examples/**" diff --git a/.github/workflows/integration-pr-ubuntu.yml b/.github/workflows/integration-pr-ubuntu.yml index ac25713e08..98724ba356 100644 --- a/.github/workflows/integration-pr-ubuntu.yml +++ b/.github/workflows/integration-pr-ubuntu.yml @@ -5,7 +5,7 @@ name: PR (Base) on: pull_request: paths-ignore: - - ".changeset/**" + - "packages/*/.changes/**" - "decisions/**" - "docs/**" - "examples/**" diff --git a/AGENTS.md b/AGENTS.md index 0758c92192..7ba6f50fc6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,18 +122,13 @@ export default [ Test both states (on/off) for future flags. Don't break existing behavior without a flag. -## Changesets +## Change Files -When making changes that affect users, create a changeset at `.changeset/.md`. If iterating on a change that hasn't shipped yet, update the existing changeset file instead of creating a new one. +When making changes that affect users, create a change file at `packages//.changes/..md`. `` should be either `patch`, `minor`, `major` or `unstable` to indicate the type of API change being made. If iterating on a change that hasn't shipped yet, update the existing change file instead of creating a new one. Format: ```markdown ---- -"react-router": patch -"@react-router/dev": minor ---- - Brief description of the change - Additional details if needed diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 60eec9eb2d..738b293c85 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -123,7 +123,7 @@ This table gives a high-level overview of the stages, but please see the individ - Because the alpha release may contain other work committed to `dev` but not yet released in a stable version, it may not be ideal for testing in all cases - In these cases, PR authors may also add the contents for a `.patch` file in a comment that folks can use via [patch-package](https://www.npmjs.com/package/patch-package) or [pnpm patch](https://pnpm.io/cli/patch) - Feedback from alpha testers is considered essential for further progress -- The PR should also contain a changeset documenting the new API for the release notes +- The PR should also contain a changes file documenting the new API for the release notes - SC members will review and approve the PR via GitHub reviews - Approval at this stage communicates: - The feature is valuable for React Router diff --git a/docs/community/contributing.md b/docs/community/contributing.md index b200c19a36..115e374ea8 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -67,6 +67,19 @@ All commits that fix bugs or add features need one or more tests. Do not merge code without tests! +### Change Files + +In order to facilitate release notes generation, every PR with a user-facing impact should include one or more change files. You can generate these vis `pnpm run changes:add` (requires node 24+). + +This tool will generate change files in the relevant `packages//.changes/` directories. Change files are markdown files with a short description of the change. They should be named using the format `..md` where type is either `patch`, `minor`, `major`, or `unstable`. For example: + +```sh +patch.fix-fetcher-redirects.md +minor.add-some-new-api.md +major.require-node-24.md +unstable.update-unstable-api.md +``` + ### Docs + Examples All commits that change or add to the API must be done in a pull request that also updates all relevant examples and docs. diff --git a/package.json b/package.json index 5ef7333212..c52c6ee3e6 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "scripts": { "build": "pnpm run --filter=\"./packages/**/*\" build", "watch": "pnpm build && pnpm run --filter=\"./packages/**/*\" --parallel build --watch", + "changes:add": "node ./scripts/changes/add.ts", "changes:publish": "node ./scripts/changes/publish.ts", "changes:pr": "node ./scripts/changes/pr.ts", "changes:preview": "node ./scripts/changes/version.ts --preview", @@ -32,12 +33,7 @@ "playwright:integration": "playwright test --config ./integration/playwright.config.ts", "vite-ecosystem-ci:build": "pnpm build", "vite-ecosystem-ci:before-test": "pnpm playwright install", - "vite-ecosystem-ci:test": "pnpm playwright:integration --project=chromium && pnpm clean:integration", - "changeset": "changeset", - "__disabled_release": "changeset publish", - "__disabled_changeset:version": "changeset version && node ./scripts/remove-prerelease-changelogs.mjs", - "__disabled_publish": "node scripts/publish.js", - "__disabled_version": "node ./scripts/version" + "vite-ecosystem-ci:test": "pnpm playwright:integration --project=chromium && pnpm clean:integration" }, "jest": { "projects": [ @@ -59,12 +55,10 @@ "@babel/preset-env": "^7.27.2", "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", - "@changesets/cli": "^2.26.2", "@eslint/compat": "^2.0.3", "@manypkg/get-packages": "^1.1.3", "@mdx-js/rollup": "^3.1.0", "@playwright/test": "^1.58.2", - "@remix-run/changelog-github": "^0.0.5", "@types/jest": "^29.5.4", "@types/jsdom": "^21.1.1", "@types/react": "catalog:", @@ -107,8 +101,6 @@ }, "pnpm": { "patchedDependencies": { - "@changesets/get-dependents-graph@1.3.6": "patches/@changesets__get-dependents-graph@1.3.6.patch", - "@changesets/assemble-release-plan": "patches/@changesets__assemble-release-plan.patch", "@mdx-js/rollup": "patches/@mdx-js__rollup.patch" }, "overrides": { diff --git a/patches/@changesets__assemble-release-plan.patch b/patches/@changesets__assemble-release-plan.patch deleted file mode 100644 index 1b35f71281..0000000000 --- a/patches/@changesets__assemble-release-plan.patch +++ /dev/null @@ -1,60 +0,0 @@ -diff --git a/dist/assemble-release-plan.cjs.dev.js b/dist/assemble-release-plan.cjs.dev.js -index e1376ca756d69816f8c79637ee7b45161f092167..314c42e8c39a34dacc3ed0c10bc7e62ca46de7d3 100644 ---- a/dist/assemble-release-plan.cjs.dev.js -+++ b/dist/assemble-release-plan.cjs.dev.js -@@ -254,10 +254,16 @@ function shouldBumpMajor({ - preInfo, - onlyUpdatePeerDependentsWhenOutOfRange - }) { -+ // PATCH: Don't do peerDependency-driven major bumps because we release in lock step -+ if (nextRelease.name === "react-router" || nextRelease.name.startsWith('@react-router/')) { -+ return false; -+ } -+ - // 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. -+ // PATCH: pass includePrerelease to incrementVersion() -+ !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..273f6bb9b46cf166f9d72058e524b4f3cbc05957 100644 ---- a/dist/assemble-release-plan.cjs.prod.js -+++ b/dist/assemble-release-plan.cjs.prod.js -@@ -130,7 +130,10 @@ 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); -+ if (nextRelease.name === "react-router" || nextRelease.name.startsWith('@react-router/')) { -+ return false; -+ } -+ 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..9a70c1ac86f530dc0cb3857d202675ed23d694b5 100644 ---- a/dist/assemble-release-plan.esm.js -+++ b/dist/assemble-release-plan.esm.js -@@ -243,10 +243,16 @@ function shouldBumpMajor({ - preInfo, - onlyUpdatePeerDependentsWhenOutOfRange - }) { -+ // PATCH: Don't do peerDependency-driven major bumps because we release in lock step -+ if (nextRelease.name === "react-router" || nextRelease.name.startsWith('@react-router/')) { -+ return false; -+ } -+ - // 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. -+ // PATCH: pass includePrerelease to incrementVersion() -+ !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"); - } - diff --git a/patches/@changesets__get-dependents-graph@1.3.6.patch b/patches/@changesets__get-dependents-graph@1.3.6.patch deleted file mode 100644 index 011ffcf31e..0000000000 --- a/patches/@changesets__get-dependents-graph@1.3.6.patch +++ /dev/null @@ -1,47 +0,0 @@ -diff --git a/dist/get-dependents-graph.cjs.dev.js b/dist/get-dependents-graph.cjs.dev.js -index 94dde7b0aa903cf4aa099acfd2fc62f6243264d9..70407b017dd86b0363af989663ff83e1ba34a925 100644 ---- a/dist/get-dependents-graph.cjs.dev.js -+++ b/dist/get-dependents-graph.cjs.dev.js -@@ -10,8 +10,14 @@ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; - var Range__default = /*#__PURE__*/_interopDefault(Range); - var chalk__default = /*#__PURE__*/_interopDefault(chalk); - --// This is a modified version of the graph-getting in bolt --const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; -+// This is a modified version of the graph-getting in bolt PATCH: Changesets -+// will check all of our internal dependencies to ensure that the versions -+// required match the current version in the repo. This doesn't work for peer -+// dependencies in our case because the compat package requires a peer -+// dependency of v4 or v5. -+// https://twitter.com/AndaristRake/status/1532379909028466688 -+const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "optionalDependencies"]; -+// const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; - - const getAllDependencies = config => { - const allDependencies = new Map(); -diff --git a/dist/get-dependents-graph.cjs.prod.js b/dist/get-dependents-graph.cjs.prod.js -index 39b2dfe0c15d052f4e342a81626c4012a695867b..dc0fed7320dd1157b3f020c06d46530547070cda 100644 ---- a/dist/get-dependents-graph.cjs.prod.js -+++ b/dist/get-dependents-graph.cjs.prod.js -@@ -14,7 +14,7 @@ function _interopDefault(e) { - - var Range__default = _interopDefault(Range), chalk__default = _interopDefault(chalk); - --const DEPENDENCY_TYPES = [ "dependencies", "devDependencies", "peerDependencies", "optionalDependencies" ], getAllDependencies = config => { -+const DEPENDENCY_TYPES = [ "dependencies", "devDependencies", "optionalDependencies" ], getAllDependencies = config => { - const allDependencies = new Map; - for (const type of DEPENDENCY_TYPES) { - const deps = config[type]; -diff --git a/dist/get-dependents-graph.esm.js b/dist/get-dependents-graph.esm.js -index 5f29582339d3a6a45ff882caf843ecf8a9d6c99d..1e4b7509e48cff7a2c702922ee5a6987e33959e0 100644 ---- a/dist/get-dependents-graph.esm.js -+++ b/dist/get-dependents-graph.esm.js -@@ -2,7 +2,7 @@ import Range from 'semver/classes/range'; - import chalk from 'chalk'; - - // This is a modified version of the graph-getting in bolt --const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; -+const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "optionalDependencies"]; - - const getAllDependencies = config => { - const allDependencies = new Map(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82947ede45..d62156eced 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,12 +47,6 @@ overrides: react-is: 19.1.0 patchedDependencies: - '@changesets/assemble-release-plan': - hash: ismjcgzyajdphoz3q5q3incsia - path: patches/@changesets__assemble-release-plan.patch - '@changesets/get-dependents-graph@1.3.6': - hash: arforfmj6nw2w4znv7h66rwf5y - path: patches/@changesets__get-dependents-graph@1.3.6.patch '@mdx-js/rollup': hash: wjxfd5pqp7spa3snsugst7roxm path: patches/@mdx-js__rollup.patch @@ -73,9 +67,6 @@ importers: '@babel/preset-typescript': specifier: ^7.27.1 version: 7.27.1(@babel/core@7.27.7) - '@changesets/cli': - specifier: ^2.26.2 - version: 2.26.2 '@eslint/compat': specifier: ^2.0.3 version: 2.0.3(eslint@10.1.0(jiti@2.4.2)) @@ -88,9 +79,6 @@ importers: '@playwright/test': specifier: ^1.58.2 version: 1.58.2 - '@remix-run/changelog-github': - specifier: ^0.0.5 - version: 0.0.5 '@types/jest': specifier: ^29.5.4 version: 29.5.12 @@ -1706,7 +1694,7 @@ importers: version: 5.4.5 vite: specifier: ^8.0.0 - version: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) playground/rsc-vite: dependencies: @@ -2038,6 +2026,9 @@ importers: '@types/node': specifier: ^22.18.0 version: 22.19.15 + '@types/prompts': + specifier: ^2.4.9 + version: 2.4.9 '@types/semver': specifier: ^7.7.0 version: 7.7.0 @@ -2773,61 +2764,9 @@ packages: '@bundled-es-modules/tough-cookie@0.1.6': resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - '@changesets/apply-release-plan@6.1.4': - resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} - - '@changesets/assemble-release-plan@5.2.4': - resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} - - '@changesets/changelog-git@0.1.14': - resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} - - '@changesets/cli@2.26.2': - resolution: {integrity: sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==} - hasBin: true - - '@changesets/config@2.3.1': - resolution: {integrity: sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==} - - '@changesets/errors@0.1.4': - resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} - - '@changesets/get-dependents-graph@1.3.6': - resolution: {integrity: sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==} - - '@changesets/get-github-info@0.5.1': - resolution: {integrity: sha512-w2yl3AuG+hFuEEmT6j1zDlg7GQLM/J2UxTmk0uJBMdRqHni4zXGe/vUlPfLom5KfX3cRfHc0hzGvloDPjWFNZw==} - - '@changesets/get-release-plan@3.0.17': - resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} - - '@changesets/get-version-range-type@0.3.2': - resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} - - '@changesets/git@2.0.0': - resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} - - '@changesets/logger@0.0.5': - resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} - - '@changesets/parse@0.3.16': - resolution: {integrity: sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==} - - '@changesets/pre@1.0.14': - resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} - - '@changesets/read@0.5.9': - resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} - '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - '@changesets/types@5.2.1': - resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} - - '@changesets/write@0.2.3': - resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} - '@cloudflare/kv-asset-handler@0.4.2': resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} @@ -3957,9 +3896,6 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@remix-run/changelog-github@0.0.5': - resolution: {integrity: sha512-43tqwUqWqirbv6D9uzo55ASPsCJ61Ein1k/M8qn+Qpros0MmbmuzjLVPmtaxfxfe2ANX0LefLvCD0pAgr1tp4g==} - '@remix-run/node-fetch-server@0.13.0': resolution: {integrity: sha512-1EsNo0ZpgXu/90AWoRZf/oE3RVTUS80tiTUpt+hv5pjtAkw7icN4WskDwz/KdAw5ARbJLMhZBrO1NqThmy/McA==} @@ -4462,9 +4398,6 @@ packages: '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - '@types/is-ci@3.0.0': - resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} - '@types/istanbul-lib-coverage@2.0.4': resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} @@ -4519,9 +4452,6 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - '@types/minimist@1.2.2': - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - '@types/morgan@1.9.10': resolution: {integrity: sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==} @@ -4540,15 +4470,15 @@ packages: '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} - '@types/normalize-package-data@2.4.1': - resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} - '@types/npmcli__package-json@4.0.4': resolution: {integrity: sha512-6QjlFUSHBmZJWuC08bz1ZCx6tm4t+7+OJXAdvM6tL2pI7n6Bh5SIp/YxQvnOLFf8MzCXs2ijyFgrzaiu1UFBGA==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/prompts@2.4.9': + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + '@types/prop-types@15.7.5': resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -4957,10 +4887,6 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -4977,10 +4903,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -5063,10 +4985,6 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - as-table@1.0.55: resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} @@ -5209,10 +5127,6 @@ packages: engines: {node: '>=10.0.0'} deprecated: Security vulnerability fixed in 5.2.1, please upgrade - better-path-resolve@1.0.0: - resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} - engines: {node: '>=4'} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -5248,9 +5162,6 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} - breakword@1.0.5: - resolution: {integrity: sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==} - browserify-zlib@0.1.4: resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} @@ -5298,10 +5209,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -5320,10 +5227,6 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -5348,9 +5251,6 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -5388,9 +5288,6 @@ packages: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} - cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -5399,10 +5296,6 @@ packages: resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} engines: {node: '>=6'} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -5413,16 +5306,10 @@ packages: collect-v8-coverage@1.0.1: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -5535,9 +5422,6 @@ packages: engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} hasBin: true - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -5568,19 +5452,6 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - csv-generate@3.4.3: - resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} - - csv-parse@4.16.3: - resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} - - csv-stringify@5.6.5: - resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} - - csv@5.5.3: - resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} - engines: {node: '>= 0.1.90'} - damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -5611,9 +5482,6 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - dataloader@1.4.0: - resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -5639,14 +5507,6 @@ packages: supports-color: optional: true - decamelize-keys@1.1.0: - resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -5674,9 +5534,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - defaults@1.0.3: - resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -5826,10 +5683,6 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} - enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} - entities@3.0.1: resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} engines: {node: '>=0.12'} @@ -5914,10 +5767,6 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} @@ -6202,13 +6051,6 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - extendable-error@0.1.7: - resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - fast-content-type-parse@2.0.1: resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} @@ -6277,9 +6119,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} - fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} @@ -6338,10 +6177,6 @@ packages: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -6474,9 +6309,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -6488,17 +6320,9 @@ packages: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} hasBin: true - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -6544,9 +6368,6 @@ packages: resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} engines: {node: '>=8'} - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - html-encoding-sniffer@3.0.0: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} @@ -6580,9 +6401,6 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} - human-id@1.0.2: - resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -6697,10 +6515,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -6777,10 +6591,6 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -6823,10 +6633,6 @@ packages: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-subdir@1.2.0: - resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} - engines: {node: '>=4'} - is-symbol@1.1.1: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} @@ -6851,10 +6657,6 @@ packages: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -7270,10 +7072,6 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} - loader-runner@4.3.1: resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} @@ -7295,9 +7093,6 @@ packages: lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -7315,9 +7110,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -7342,14 +7134,6 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -7445,10 +7229,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - meow@6.1.1: - resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} - engines: {node: '>=8'} - merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -7693,10 +7473,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -7704,10 +7480,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mixme@0.5.4: - resolution: {integrity: sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==} - engines: {node: '>= 8.0.0'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -7782,15 +7554,6 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - node-fetch@2.6.7: - resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -7805,9 +7568,6 @@ packages: resolution: {integrity: sha512-Sla56CeSLWvPbwud2kogqf5edQtKNXZBtXDDpmOzAgNZjwETbK/Am6PXfs54iZPLBm8K8amZ9XWaCQwGqZmKyQ==} engines: {node: '>=6.6.0'} - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -7887,13 +7647,6 @@ packages: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - outdent@0.5.0: - resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -7901,10 +7654,6 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -7921,10 +7670,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - p-map@7.0.3: resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} engines: {node: '>=18'} @@ -8104,10 +7849,6 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} - preferred-pm@3.0.3: - resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} - engines: {node: '>=10'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -8117,11 +7858,6 @@ packages: engines: {node: '>=6'} hasBin: true - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - prettier@3.6.2: resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} @@ -8169,9 +7905,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -8209,10 +7942,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - random-bytes@1.0.0: resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} engines: {node: '>= 0.8'} @@ -8274,14 +8003,6 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -8409,9 +8130,6 @@ packages: require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} - require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -8531,10 +8249,6 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} - semver@5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -8555,9 +8269,6 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} @@ -8584,18 +8295,10 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -8646,11 +8349,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smartwrap@2.0.2: - resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} - engines: {node: '>=6'} - hasBin: true - socks-proxy-agent@8.0.4: resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==} engines: {node: '>= 14'} @@ -8692,18 +8390,9 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spawndamnit@2.0.0: - resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} - - spdx-correct@3.1.1: - resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} - spdx-exceptions@2.3.0: resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-expression-parse@4.0.0: resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} @@ -8749,9 +8438,6 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - stream-transform@2.1.3: - resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} - strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -8859,10 +8545,6 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -8889,10 +8571,6 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - term-size@2.2.1: - resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} - terser-webpack-plugin@5.3.15: resolution: {integrity: sha512-PGkOdpRFK+rb1TzVz+msVhw4YMRT9txLF4kRqvJhGhCM324xuR3REBSHALN+l+sAhKUmz0aotnjp5D+P83mLhQ==} engines: {node: '>= 10.13.0'} @@ -8952,10 +8630,6 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -8978,9 +8652,6 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -8995,10 +8666,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - trough@2.1.0: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} @@ -9064,11 +8731,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tty-table@4.1.6: - resolution: {integrity: sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==} - engines: {node: '>=8.0.0'} - hasBin: true - turbo-stream@3.1.0: resolution: {integrity: sha512-tVI25WEXl4fckNEmrq70xU1XumxUwEx/FZD5AgEcV8ri7Wvrg2o7GEq8U7htrNx3CajciGm+kDyhRf5JB6t7/A==} @@ -9080,10 +8742,6 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -9092,14 +8750,6 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} @@ -9303,9 +8953,6 @@ packages: typescript: optional: true - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vandium-utils@1.2.0: resolution: {integrity: sha512-yxYUDZz4BNo0CW/z5w4mvclitt5zolY7zjW97i6tTE+sU63cxYs1A6Bl9+jtIQa3+0hkeqY87k+7ptRvmeHe3g==} @@ -9597,9 +9244,6 @@ packages: resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} engines: {node: '>=10.13.0'} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - web-encoding@1.1.5: resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} @@ -9607,9 +9251,6 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -9653,9 +9294,6 @@ packages: resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==} engines: {node: '>=14'} - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -9671,21 +9309,10 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-module@2.0.0: - resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} - - which-pm@2.0.0: - resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} - engines: {node: '>=8.15'} - which-typed-array@1.1.20: resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -9758,16 +9385,10 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -9780,18 +9401,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -10771,162 +10384,8 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@changesets/apply-release-plan@6.1.4': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/config': 2.3.1 - '@changesets/get-version-range-type': 0.3.2 - '@changesets/git': 2.0.0 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - detect-indent: 6.1.0 - fs-extra: 7.0.1 - lodash.startcase: 4.4.0 - outdent: 0.5.0 - prettier: 2.8.8 - resolve-from: 5.0.0 - semver: 7.7.4 - - '@changesets/assemble-release-plan@5.2.4(patch_hash=ismjcgzyajdphoz3q5q3incsia)': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.6(patch_hash=arforfmj6nw2w4znv7h66rwf5y) - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - semver: 7.7.4 - - '@changesets/changelog-git@0.1.14': - dependencies: - '@changesets/types': 5.2.1 - - '@changesets/cli@2.26.2': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/apply-release-plan': 6.1.4 - '@changesets/assemble-release-plan': 5.2.4(patch_hash=ismjcgzyajdphoz3q5q3incsia) - '@changesets/changelog-git': 0.1.14 - '@changesets/config': 2.3.1 - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.6(patch_hash=arforfmj6nw2w4znv7h66rwf5y) - '@changesets/get-release-plan': 3.0.17 - '@changesets/git': 2.0.0 - '@changesets/logger': 0.0.5 - '@changesets/pre': 1.0.14 - '@changesets/read': 0.5.9 - '@changesets/types': 5.2.1 - '@changesets/write': 0.2.3 - '@manypkg/get-packages': 1.1.3 - '@types/is-ci': 3.0.0 - '@types/semver': 7.7.0 - ansi-colors: 4.1.3 - chalk: 2.4.2 - enquirer: 2.3.6 - external-editor: 3.1.0 - fs-extra: 7.0.1 - human-id: 1.0.2 - is-ci: 3.0.1 - meow: 6.1.1 - outdent: 0.5.0 - p-limit: 2.3.0 - preferred-pm: 3.0.3 - resolve-from: 5.0.0 - semver: 7.7.4 - spawndamnit: 2.0.0 - term-size: 2.2.1 - tty-table: 4.1.6 - - '@changesets/config@2.3.1': - dependencies: - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.6(patch_hash=arforfmj6nw2w4znv7h66rwf5y) - '@changesets/logger': 0.0.5 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - micromatch: 4.0.5 - - '@changesets/errors@0.1.4': - dependencies: - extendable-error: 0.1.7 - - '@changesets/get-dependents-graph@1.3.6(patch_hash=arforfmj6nw2w4znv7h66rwf5y)': - dependencies: - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - chalk: 2.4.2 - fs-extra: 7.0.1 - semver: 7.7.4 - - '@changesets/get-github-info@0.5.1': - dependencies: - dataloader: 1.4.0 - node-fetch: 2.6.7 - transitivePeerDependencies: - - encoding - - '@changesets/get-release-plan@3.0.17': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/assemble-release-plan': 5.2.4(patch_hash=ismjcgzyajdphoz3q5q3incsia) - '@changesets/config': 2.3.1 - '@changesets/pre': 1.0.14 - '@changesets/read': 0.5.9 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - - '@changesets/get-version-range-type@0.3.2': {} - - '@changesets/git@2.0.0': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - micromatch: 4.0.5 - spawndamnit: 2.0.0 - - '@changesets/logger@0.0.5': - dependencies: - chalk: 2.4.2 - - '@changesets/parse@0.3.16': - dependencies: - '@changesets/types': 5.2.1 - js-yaml: 3.14.1 - - '@changesets/pre@1.0.14': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - - '@changesets/read@0.5.9': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/git': 2.0.0 - '@changesets/logger': 0.0.5 - '@changesets/parse': 0.3.16 - '@changesets/types': 5.2.1 - chalk: 2.4.2 - fs-extra: 7.0.1 - p-filter: 2.1.0 - '@changesets/types@4.1.0': {} - '@changesets/types@5.2.1': {} - - '@changesets/write@0.2.3': - dependencies: - '@babel/runtime': 7.24.1 - '@changesets/types': 5.2.1 - fs-extra: 7.0.1 - human-id: 1.0.2 - prettier: 2.8.8 - '@cloudflare/kv-asset-handler@0.4.2': {} '@cloudflare/unenv-preset@2.15.0(unenv@2.0.0-rc.24)(workerd@1.20250705.0)': @@ -11895,15 +11354,6 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@remix-run/changelog-github@0.0.5': - dependencies: - '@changesets/errors': 0.1.4 - '@changesets/get-github-info': 0.5.1 - '@changesets/types': 5.2.1 - dotenv: 8.6.0 - transitivePeerDependencies: - - encoding - '@remix-run/node-fetch-server@0.13.0': {} '@remix-run/web-blob@3.1.0': @@ -12345,10 +11795,6 @@ snapshots: '@types/http-errors@2.0.4': {} - '@types/is-ci@3.0.0': - dependencies: - ci-info: 3.8.0 - '@types/istanbul-lib-coverage@2.0.4': {} '@types/istanbul-lib-report@3.0.0': @@ -12406,8 +11852,6 @@ snapshots: '@types/minimatch@5.1.2': {} - '@types/minimist@1.2.2': {} - '@types/morgan@1.9.10': dependencies: '@types/node': 20.19.37 @@ -12428,12 +11872,15 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/normalize-package-data@2.4.1': {} - '@types/npmcli__package-json@4.0.4': {} '@types/parse-json@4.0.2': {} + '@types/prompts@2.4.9': + dependencies: + '@types/node': 22.19.15 + kleur: 3.0.3 + '@types/prop-types@15.7.5': {} '@types/qs@6.9.14': {} @@ -13181,8 +12628,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -13195,10 +12640,6 @@ snapshots: ansi-regex@6.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -13303,8 +12744,6 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - arrify@1.0.1: {} - as-table@1.0.55: dependencies: printable-characters: 1.0.42 @@ -13500,10 +12939,6 @@ snapshots: basic-ftp@5.0.5: {} - better-path-resolve@1.0.0: - dependencies: - is-windows: 1.0.2 - binary-extensions@2.3.0: {} bl@4.1.0: @@ -13554,10 +12989,6 @@ snapshots: dependencies: fill-range: 7.0.1 - breakword@1.0.5: - dependencies: - wcwidth: 1.0.1 - browserify-zlib@0.1.4: dependencies: pako: 0.2.9 @@ -13609,12 +13040,6 @@ snapshots: callsites@3.1.0: {} - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -13625,12 +13050,6 @@ snapshots: chai@6.2.2: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -13651,8 +13070,6 @@ snapshots: character-reference-invalid@2.0.1: {} - chardet@0.7.0: {} - cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -13706,12 +13123,6 @@ snapshots: cli-width@4.1.0: {} - cliui@6.0.0: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -13724,24 +13135,16 @@ snapshots: kind-of: 6.0.3 shallow-clone: 3.0.1 - clone@1.0.4: {} - co@4.6.0: {} collapse-white-space@2.1.0: {} collect-v8-coverage@1.0.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} combined-stream@1.0.8: @@ -13843,12 +13246,6 @@ snapshots: dependencies: cross-spawn: 7.0.6 - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -13881,19 +13278,6 @@ snapshots: csstype@3.2.3: {} - csv-generate@3.4.3: {} - - csv-parse@4.16.3: {} - - csv-stringify@5.6.5: {} - - csv@5.5.3: - dependencies: - csv-generate: 3.4.3 - csv-parse: 4.16.3 - csv-stringify: 5.6.5 - stream-transform: 2.1.3 - damerau-levenshtein@1.0.8: {} data-uri-to-buffer@2.0.2: {} @@ -13926,8 +13310,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - dataloader@1.4.0: {} - debug@2.6.9: dependencies: ms: 2.0.0 @@ -13940,13 +13322,6 @@ snapshots: dependencies: ms: 2.1.3 - decamelize-keys@1.1.0: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - decimal.js@10.4.3: {} decode-named-character-reference@1.0.2: @@ -13965,10 +13340,6 @@ snapshots: deepmerge@4.3.1: {} - defaults@1.0.3: - dependencies: - clone: 1.0.4 - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -14107,10 +13478,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 - enquirer@2.3.6: - dependencies: - ansi-colors: 4.1.3 - entities@3.0.1: {} entities@4.5.0: {} @@ -14357,8 +13724,6 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: {} escape-string-regexp@4.0.0: {} @@ -14862,14 +14227,6 @@ snapshots: extend@3.0.2: {} - extendable-error@0.1.7: {} - - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - fast-content-type-parse@2.0.1: {} fast-deep-equal@3.1.3: {} @@ -14944,11 +14301,6 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - find-yarn-workspace-root2@1.2.16: - dependencies: - micromatch: 4.0.5 - pkg-dir: 4.2.0 - fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 @@ -15005,12 +14357,6 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.0 - fs-extra@7.0.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - fs-extra@8.1.0: dependencies: graceful-fs: 4.2.11 @@ -15169,8 +14515,6 @@ snapshots: graceful-fs@4.2.11: {} - grapheme-splitter@1.0.4: {} - graphemer@1.4.0: {} graphql@16.9.0: {} @@ -15184,12 +14528,8 @@ snapshots: pumpify: 1.5.1 through2: 2.0.5 - hard-rejection@2.1.0: {} - has-bigints@1.0.2: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -15265,8 +14605,6 @@ snapshots: hexoid@1.0.0: {} - hosted-git-info@2.8.9: {} - html-encoding-sniffer@3.0.0: dependencies: whatwg-encoding: 2.0.0 @@ -15319,8 +14657,6 @@ snapshots: transitivePeerDependencies: - supports-color - human-id@1.0.2: {} - human-signals@2.1.0: {} human-signals@8.0.1: {} @@ -15420,10 +14756,6 @@ snapshots: is-callable@1.2.7: {} - is-ci@3.0.1: - dependencies: - ci-info: 3.8.0 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -15482,8 +14814,6 @@ snapshots: is-path-inside@3.0.3: {} - is-plain-obj@1.1.0: {} - is-plain-obj@2.1.0: {} is-plain-obj@4.1.0: {} @@ -15520,10 +14850,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-subdir@1.2.0: - dependencies: - better-path-resolve: 1.0.0 - is-symbol@1.1.1: dependencies: call-bound: 1.0.4 @@ -15547,8 +14873,6 @@ snapshots: call-bind: 1.0.8 get-intrinsic: 1.3.0 - is-windows@1.0.2: {} - isarray@1.0.0: {} isarray@2.0.5: {} @@ -15925,7 +15249,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -16153,13 +15477,6 @@ snapshots: load-tsconfig@0.2.5: {} - load-yaml-file@0.2.0: - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - loader-runner@4.3.1: {} locate-path@5.0.0: @@ -16176,8 +15493,6 @@ snapshots: lodash.sortby@4.7.0: {} - lodash.startcase@4.4.0: {} - lodash@4.17.21: {} log-update@5.0.1: @@ -16196,11 +15511,6 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -16223,10 +15533,6 @@ snapshots: dependencies: tmpl: 1.0.5 - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - markdown-extensions@2.0.0: {} markdown-it@13.0.1: @@ -16458,20 +15764,6 @@ snapshots: media-typer@0.3.0: {} - meow@6.1.1: - dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.0 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 2.5.0 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.13.1 - yargs-parser: 18.1.3 - merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} @@ -16950,18 +16242,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.8: {} minipass@7.1.2: {} - mixme@0.5.4: {} - mkdirp-classic@0.5.3: {} mlly@1.7.4: @@ -17066,10 +16350,6 @@ snapshots: netmask@2.0.2: {} - node-fetch@2.6.7: - dependencies: - whatwg-url: 5.0.0 - node-int64@0.4.0: {} node-mocks-http@1.14.1: @@ -17091,13 +16371,6 @@ snapshots: node-webtokens@1.0.4: {} - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.10 - semver: 5.7.1 - validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} npm-run-path@4.0.1: @@ -17188,10 +16461,6 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - os-tmpdir@1.0.2: {} - - outdent@0.5.0: {} - outvariant@1.4.3: {} own-keys@1.0.1: @@ -17200,10 +16469,6 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-filter@2.1.0: - dependencies: - p-map: 2.1.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -17220,8 +16485,6 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@2.1.0: {} - p-map@7.0.3: {} p-try@2.2.0: {} @@ -17396,19 +16659,10 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preferred-pm@3.0.3: - dependencies: - find-up: 5.0.0 - find-yarn-workspace-root2: 1.2.16 - path-exists: 4.0.0 - which-pm: 2.0.0 - prelude-ls@1.2.1: {} premove@4.0.0: {} - prettier@2.8.8: {} - prettier@3.6.2: {} pretty-format@27.5.1: @@ -17470,8 +16724,6 @@ snapshots: proxy-from-env@1.1.0: {} - pseudomap@1.0.2: {} - psl@1.9.0: {} pump@2.0.1: @@ -17508,8 +16760,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@4.0.1: {} - random-bytes@1.0.0: {} randombytes@2.1.0: @@ -17582,19 +16832,6 @@ snapshots: dependencies: pify: 2.3.0 - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.1 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -17755,8 +16992,6 @@ snapshots: require-like@0.1.2: {} - require-main-filename@2.0.0: {} - requires-port@1.0.0: {} reserved-identifiers@1.2.0: {} @@ -17905,8 +17140,6 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) - semver@5.7.1: {} - semver@6.3.1: {} semver@7.7.4: {} @@ -17942,8 +17175,6 @@ snapshots: transitivePeerDependencies: - supports-color - set-blocking@2.0.0: {} - set-cookie-parser@2.6.0: {} set-function-length@1.2.2: @@ -18005,16 +17236,10 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} shelljs@0.8.5: @@ -18068,15 +17293,6 @@ snapshots: smart-buffer@4.2.0: {} - smartwrap@2.0.2: - dependencies: - array.prototype.flat: 1.3.3 - breakword: 1.0.5 - grapheme-splitter: 1.0.4 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - yargs: 15.4.1 - socks-proxy-agent@8.0.4: dependencies: agent-base: 7.1.1 @@ -18123,23 +17339,8 @@ snapshots: space-separated-tokens@2.0.2: {} - spawndamnit@2.0.0: - dependencies: - cross-spawn: 5.1.0 - signal-exit: 3.0.7 - - spdx-correct@3.1.1: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.12 - spdx-exceptions@2.3.0: {} - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.12 - spdx-expression-parse@4.0.0: dependencies: spdx-exceptions: 2.3.0 @@ -18177,10 +17378,6 @@ snapshots: stream-shift@1.0.3: {} - stream-transform@2.1.3: - dependencies: - mixme: 0.5.4 - strict-event-emitter@0.5.1: {} string-length@4.0.2: @@ -18329,10 +17526,6 @@ snapshots: supports-color@10.2.2: {} - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -18362,8 +17555,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - term-size@2.2.1: {} - terser-webpack-plugin@5.3.15(@swc/core@1.11.24)(esbuild@0.27.4)(webpack@5.103.0(@swc/core@1.11.24)(esbuild@0.27.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -18439,10 +17630,6 @@ snapshots: tinyrainbow@3.1.0: {} - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -18465,8 +17652,6 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 - tr46@0.0.3: {} - tr46@1.0.1: dependencies: punycode: 2.3.0 @@ -18479,8 +17664,6 @@ snapshots: trim-lines@3.0.1: {} - trim-newlines@3.0.1: {} - trough@2.1.0: {} ts-api-utils@2.5.0(typescript@5.4.5): @@ -18547,16 +17730,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tty-table@4.1.6: - dependencies: - chalk: 4.1.2 - csv: 5.5.3 - kleur: 4.1.5 - smartwrap: 2.0.2 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - yargs: 17.7.2 - turbo-stream@3.1.0: {} type-check@0.4.0: @@ -18565,16 +17738,10 @@ snapshots: type-detect@4.0.8: {} - type-fest@0.13.1: {} - type-fest@0.20.2: {} type-fest@0.21.3: {} - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - type-fest@1.4.0: {} type-fest@4.40.1: {} @@ -18815,11 +17982,6 @@ snapshots: optionalDependencies: typescript: 5.4.5 - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.1.1 - spdx-expression-parse: 3.0.1 - vandium-utils@1.2.0: {} vandium-utils@2.0.0: {} @@ -19235,10 +18397,6 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - wcwidth@1.0.1: - dependencies: - defaults: 1.0.3 - web-encoding@1.1.5: dependencies: util: 0.12.5 @@ -19247,8 +18405,6 @@ snapshots: web-streams-polyfill@3.3.3: {} - webidl-conversions@3.0.1: {} - webidl-conversions@4.0.2: {} webidl-conversions@7.0.0: {} @@ -19368,11 +18524,6 @@ snapshots: tr46: 4.1.1 webidl-conversions: 7.0.0 - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 @@ -19410,13 +18561,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.3 - which-module@2.0.0: {} - - which-pm@2.0.0: - dependencies: - load-yaml-file: 0.2.0 - path-exists: 4.0.0 - which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 @@ -19427,10 +18571,6 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -19506,39 +18646,16 @@ snapshots: xtend@4.0.2: {} - y18n@4.0.3: {} - y18n@5.0.8: {} - yallist@2.1.2: {} - yallist@3.1.1: {} yaml@1.10.2: {} yaml@2.8.0: {} - yargs-parser@18.1.3: - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - yargs-parser@21.1.1: {} - yargs@15.4.1: - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.0 - y18n: 4.0.3 - yargs-parser: 18.1.3 - yargs@17.7.2: dependencies: cliui: 8.0.1 diff --git a/scripts/changes/add.ts b/scripts/changes/add.ts new file mode 100644 index 0000000000..3f40580ddb --- /dev/null +++ b/scripts/changes/add.ts @@ -0,0 +1,138 @@ +/** + * Interactive script to create a change file + * + * Usage: + * node scripts/changes/add.ts + */ +import * as fs from "node:fs"; +import * as path from "node:path"; +import prompts from "prompts"; +import { getAllPackageDirNames, getPackagePath } from "../utils/packages.ts"; + +const bumpTypes = ["patch", "minor", "major", "unstable"] as const; + +interface Package { + dirName: string; + name: string; +} + +console.log("\nCreate a change file\n"); + +let packages = getPackages(); + +// Abort cleanly on Ctrl-C +let cancelled = false; +const onCancel = () => { + cancelled = true; + return false; // stops prompts from throwing +}; + +// 1. Select packages +const { selectedPackages } = await prompts( + { + type: "multiselect", + name: "selectedPackages", + message: "Select packages", + choices: packages.map((pkg) => ({ title: pkg.name, value: pkg })), + min: 1, + hint: "Space to select, arrow keys to navigate, Enter to confirm", + }, + { onCancel }, +); + +if (cancelled || !selectedPackages) process.exit(0); + +// 2. Select bump type +const { bump } = await prompts( + { + type: "select", + name: "bump", + message: "Change type", + choices: bumpTypes.map((t) => ({ title: t, value: t })), + initial: 0, + }, + { onCancel }, +); + +if (cancelled || bump == null) process.exit(0); + +// 3. Description +const { description } = await prompts( + { + type: "text", + name: "description", + message: "Description", + validate: (v: string) => + v.trim().length > 0 ? true : "Description cannot be empty", + }, + { onCancel }, +); + +if (cancelled || description == null) process.exit(0); + +// 4. Derive slug and write files +let slug = toSlug(description.trim()); +let fileName = `${bump}.${slug}.md`; + +console.log(); +for (let pkg of selectedPackages as Package[]) { + let changesDir = path.join(getPackagePath(pkg.dirName), ".changes"); + let filePath = path.join(changesDir, fileName); + + if (!fs.existsSync(changesDir)) { + fs.mkdirSync(changesDir, { recursive: true }); + } + + if (fs.existsSync(filePath)) { + console.warn( + `โš ๏ธ File already exists, skipping: packages/${pkg.dirName}/.changes/${fileName}`, + ); + continue; + } + + fs.writeFileSync(filePath, description.trim() + "\n", "utf-8"); + console.log(`โœ… ${pkg.name}: packages/${pkg.dirName}/.changes/${fileName}`); +} + +console.log(); + +// --- Utils --- + +function getPackages(): Package[] { + return getAllPackageDirNames() + .map((dirName) => { + let pkgJsonPath = path.join(getPackagePath(dirName), "package.json"); + if (!fs.existsSync(pkgJsonPath)) return null; + let { name } = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")); + return { dirName, name } as Package; + }) + .filter((p): p is Package => p !== null) + .sort((a, b) => { + const order = (name: string) => { + if (name === "react-router") return 0; + if (name === "react-router-dom") return 1; + if (name.startsWith("@react-router/")) return 2; + return 3; + }; + const oa = order(a.name); + const ob = order(b.name); + if (oa !== ob) return oa - ob; + return a.name.localeCompare(b.name); + }); +} + +/** + * Converts a free-text description into a kebab-case slug of at most 6 words. + * Non-alphanumeric characters (other than spaces) are stripped before slugging. + */ +function toSlug(description: string): string { + return ( + description + .toLowerCase() + .replace(/[^a-z0-9\s]/g, "") + .trim() + .split(/\s+/) + .slice(0, 6) + .join("-") || "change" + ); +} diff --git a/scripts/changes/check-pr.ts b/scripts/changes/check-pr.ts new file mode 100644 index 0000000000..d3c0c095ea --- /dev/null +++ b/scripts/changes/check-pr.ts @@ -0,0 +1,81 @@ +/** + * Checks whether the current PR contains a change file and posts (or + * updates) a sticky comment on the PR with the result. + * + * Usage (called by the changes-file GitHub Actions workflow): + * node scripts/changes/check-pr.ts + * + * Usage: + * node scripts/changes/check-pr.ts + * + * Environment: + * GITHUB_TOKEN - Required. GitHub token with pull-requests:write permission. + */ +import { + createPrComment, + getPrComments, + getPrFiles, + updatePrComment, +} from "../utils/github.ts"; + +const COMMENT_MARKER = ""; + +const COMMENT_FOUND = `${COMMENT_MARKER} +## โœ… Change File Found + +A \`.changes\` file has been found in this PR. Thanks!`; + +const COMMENT_MISSING = `${COMMENT_MARKER} +## ๐Ÿ“ No Change File Found + +This PR doesn't include a change file which is used for automated release notes. +If your change affects users, please add one (or more) and commit the generated file(s). + +\`\`\`sh +pnpm run changes:add +\`\`\` + +> This script requires Node 24+. If you are on a lower version, please [add a file manually](https://reactrouter.com/community/contributing#change-files) + +> Not every PR needs a change file โ€” you can skip this step if the change is internal-only +> (tests, tooling, docs)\ +`; + +// Matches packages/*/.changes/*.md but not .gitkeep +const CHANGE_FILE_RE = /^packages\/[^/]+\/\.changes\/[^/]+\.md$/; + +async function main() { + let arg = process.argv[2]; + let prNumber = arg ? parseInt(arg, 10) : NaN; + if (!arg || isNaN(prNumber)) { + console.error("Usage: node scripts/changes/check-pr.ts "); + process.exit(1); + } + + // Check for change files via the GitHub API โ€” no git fetch needed + let files = await getPrFiles(prNumber); + let found = files.some((f) => CHANGE_FILE_RE.test(f.filename)); + let body = found ? COMMENT_FOUND : COMMENT_MISSING; + console.log(`Change files found: ${found}`); + + // Find existing sticky comment + let comments = await getPrComments(prNumber); + let existing = comments.find( + (c) => + c.user?.login === "github-actions[bot]" && + c.body?.includes(COMMENT_MARKER), + ); + + if (existing) { + console.log(`Updating existing comment #${existing.id}`); + await updatePrComment(existing.id, body); + } else { + console.log("Creating new comment"); + await createPrComment(prNumber, body); + } +} + +main().catch((err) => { + console.error("Error:", err.message); + process.exit(1); +}); diff --git a/scripts/find-release-from-changeset.js b/scripts/find-release-from-changeset.js deleted file mode 100644 index fe923aec47..0000000000 --- a/scripts/find-release-from-changeset.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * - * @param {string | undefined} publishedPackages - * @param {string | undefined} packageVersionToFollow - * @returns {string | undefined} - */ -function findReleaseFromChangeset(publishedPackages, packageVersionToFollow) { - if (!publishedPackages) { - throw new Error("No published packages found"); - } - - let packages = JSON.parse(publishedPackages); - - if (!Array.isArray(packages)) { - throw new Error("Published packages is not an array"); - } - - /** @see https://github.com/changesets/action#outputs */ - /** @type { { name: string; version: string }[] } */ - let typed = packages.filter((pkg) => "name" in pkg && "version" in pkg); - - let found = typed.find((pkg) => pkg.name === packageVersionToFollow); - - if (!found) { - throw new Error( - `${packageVersionToFollow} was not found in the published packages`, - ); - } - - console.log(found.version); - return found.version; -} - -findReleaseFromChangeset( - process.env.PUBLISHED_PACKAGES, - process.env.PACKAGE_VERSION_TO_FOLLOW, -); diff --git a/scripts/finish-stable-release.sh b/scripts/finish-stable-release.sh deleted file mode 100755 index c23bbd32ad..0000000000 --- a/scripts/finish-stable-release.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -set -x -set -e - -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) -if [[ "${CURRENT_BRANCH}" != "release-next" ]]; then - echo "Error: Script must be run from the 'release-next' branch." - exit 1 -fi - -if [[ -n $(git status --porcelain) ]]; then - echo "Error: Your git working directory is not clean. Please commit or stash your changes." - exit 1 -fi - -git pull -git checkout main -git pull -git merge release-next --no-edit - -if [[ -n $(git status --porcelain) ]]; then - echo "Error: Your git working directory is not clean after the merge to main." - exit 1 -fi - -git push - -git checkout dev -git pull -git merge release-next --no-edit - -if [[ -n $(git status --porcelain) ]]; then - echo "Error: Your git working directory is not clean after the merge to dev." - exit 1 -fi - -git push - -git branch -d release-next - -if [[ -n $(git show-ref refs/heads/changeset-release/release-next) ]]; then - git branch -D changeset-release/release-next -fi - -./scripts/delete-pre-tags.sh -./scripts/delete-nightly-tags.sh - -set +e -set +x \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index dd433e0a13..7509e889af 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { "@types/node": "^22.18.0", + "@types/prompts": "^2.4.9", "@types/semver": "^7.7.0" }, "engines": { diff --git a/scripts/remove-prerelease-changelogs.mjs b/scripts/remove-prerelease-changelogs.mjs deleted file mode 100644 index 7a599ab933..0000000000 --- a/scripts/remove-prerelease-changelogs.mjs +++ /dev/null @@ -1,123 +0,0 @@ -import * as fs from "node:fs"; -import path from "node:path"; -import * as url from "node:url"; -import { getPackagesSync } from "@manypkg/get-packages"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import rehypeStringify from "remark-stringify"; -import { unified } from "unified"; -import { remove } from "unist-util-remove"; - -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -const rootDir = path.join(__dirname, ".."); - -main(); - -async function main() { - if (isPrereleaseMode()) { - console.log("๐Ÿšซ Skipping changelog removal in prerelease mode"); - return; - } - await removePreReleaseChangelogs(); - console.log("โœ… Removed pre-release changelogs"); -} - -async function removePreReleaseChangelogs() { - let allPackages = getPackagesSync(rootDir).packages; - - /** @type {Promise[]} */ - let processes = []; - for (let pkg of allPackages) { - let changelogPath = path.join(pkg.dir, "CHANGELOG.md"); - if (!fs.existsSync(changelogPath)) { - continue; - } - let changelogFileContents = fs.readFileSync(changelogPath, "utf-8"); - processes.push( - (async () => { - let file = await unified() - // Since we have multiple versions of remark-parse, TS resolves to the - // wrong one - // @ts-expect-error - .use(remarkParse) - .use(remarkGfm) - .use(removePreReleaseSectionFromMarkdown) - // same problem - // @ts-expect-error - .use(rehypeStringify, { - bullet: "-", - emphasis: "_", - listItemIndent: "one", - }) - .process(changelogFileContents); - - let fileContents = file.toString(); - await fs.promises.writeFile(changelogPath, fileContents, "utf-8"); - })(), - ); - } - return Promise.all(processes); -} - -function removePreReleaseSectionFromMarkdown() { - /** - * @param {import('./unist').RootNode} tree - * @returns {Promise} - */ - async function transformer(tree) { - remove( - tree, - /** - * @param {import("./unist").Node & { __REMOVE__?: boolean }} node - * @param {number | null | undefined} index - * @param {*} parent - */ - (node, index, parent) => { - if (node.__REMOVE__ === true) return true; - if ( - node.type === "heading" && - node.depth === 2 && - node.children[0].type === "text" && - isPrereleaseVersion(node.children[0].value) - ) { - if (index == null || parent == null) return false; - - let nextIdx = 1; - let nextNode = parent.children[index + 1]; - let found = false; - - /** @type {import('./unist').FlowNode[]} */ - while (nextNode && !found) { - if (nextNode.type === "heading" && nextNode.depth === 2) { - found = true; - break; - } - nextNode.__REMOVE__ = true; - nextNode = parent.children[++nextIdx + index]; - } - return true; - } - - return false; - }, - ); - } - return transformer; -} - -/** - * @param {string} str - * @returns - */ -function isPrereleaseVersion(str) { - return /^(v?\d+\.){2}\d+-[a-z]+\.\d+$/i.test(str.trim()); -} - -function isPrereleaseMode() { - try { - let prereleaseFilePath = path.join(rootDir, ".changeset", "pre.json"); - return fs.existsSync(prereleaseFilePath); - } catch (err) { - return false; - } -} diff --git a/scripts/start-prerelease.sh b/scripts/start-prerelease.sh deleted file mode 100755 index 9cbb7d891d..0000000000 --- a/scripts/start-prerelease.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -set -x -set -e - -if [[ -n $(git status --porcelain) ]]; then - echo "Error: Your git working directory is not clean. Please commit or stash your changes." - exit 1 -fi - -git checkout main -git pull -git checkout dev -git pull -git checkout -b release-next -git merge main --no-edit - -if [[ -n $(git status --porcelain) ]]; then - echo "Error: Your git working directory is not clean after merging main ito release-next." - exit 1 -fi - -pnpm changeset pre enter pre -git add .changeset/pre.json -git commit -m "Enter prerelease mode" -git push --set-upstream origin release-next - -set +e -set +x \ No newline at end of file diff --git a/scripts/utils/github.ts b/scripts/utils/github.ts index d2df114c9d..5b5e413933 100644 --- a/scripts/utils/github.ts +++ b/scripts/utils/github.ts @@ -189,6 +189,22 @@ export async function updatePrComment(commentId: number, body: string) { }); } +/** + * Get all files changed in a PR + */ +export async function getPrFiles(prNumber: number) { + let response = await request( + "GET /repos/{owner}/{repo}/pulls/{pull_number}/files", + { + ...requestOptions(), + pull_number: prNumber, + per_page: 100, + }, + ); + + return response.data; +} + /** * Delete a comment on a PR */ From a224b8d41b0c406cfa6d3205b662d578d3a1aa60 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 14 Apr 2026 13:26:40 -0400 Subject: [PATCH 06/36] Update migrate changesets script to include SHA/PR number --- scripts/changes/migrate-changesets.ts | 40 +++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/scripts/changes/migrate-changesets.ts b/scripts/changes/migrate-changesets.ts index f9b347972d..d8a38a20e1 100644 --- a/scripts/changes/migrate-changesets.ts +++ b/scripts/changes/migrate-changesets.ts @@ -17,10 +17,14 @@ * node scripts/changes/migrate-changesets-files.ts [--dry-run] */ +import * as cp from "node:child_process"; import * as fs from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import { packageNameToDirectoryName } from "../utils/packages.ts"; +import { + GITHUB_REPO_URL, + packageNameToDirectoryName, +} from "../utils/packages.ts"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, "..", ".."); @@ -74,6 +78,34 @@ function parseChangeset(content: string): ParsedChangeset | null { return { packages, description }; } +/** + * Returns a markdown link to the PR or commit that introduced the given file. + * Prefers a PR link like `([#123](...))`, falls back to a short SHA link. + */ +function getSourceLink(filePath: string): string { + let result = cp.spawnSync( + "git", + ["log", "--format=%H %s", "-1", "--", filePath], + { encoding: "utf-8" }, + ); + + let line = result.stdout.trim(); + if (!line) return ""; + + let spaceIndex = line.indexOf(" "); + let sha = line.slice(0, spaceIndex); + let subject = line.slice(spaceIndex + 1); + + let prMatch = subject.match(/\(#(\d+)\)$/); + if (prMatch) { + let pr = prMatch[1]; + return ` ([#${pr}](${GITHUB_REPO_URL}/pull/${pr}))`; + } + + let shortSha = sha.slice(0, 8); + return ` ([${shortSha}](${GITHUB_REPO_URL}/commit/${sha}))`; +} + function slugify(name: string): string { return name .toLowerCase() @@ -185,7 +217,11 @@ async function main() { console.log(` โœ… ${packageName} โ†’ ${destRelative}`); if (!dryRun) { - fs.writeFileSync(destPath, parsed.description + "\n", "utf-8"); + let link = getSourceLink(filePath); + let lines = parsed.description.trim().split("\n"); + lines[0] += link; + let content = lines.join("\n") + "\n"; + fs.writeFileSync(destPath, content, "utf-8"); } totalCreated++; From c54ebc1b633445c3e691f6dec511dbc402cb7457 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Tue, 14 Apr 2026 17:27:51 +0000 Subject: [PATCH 07/36] chore: deduplicate `pnpm-lock.yaml` --- pnpm-lock.yaml | 401 ++++++++++++++----------------------------------- 1 file changed, 110 insertions(+), 291 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d62156eced..ad674af5e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,7 +114,7 @@ importers: version: 10.1.0(jiti@2.4.2) eslint-config-react-app: specifier: ^7.0.1 - version: 7.0.1(@babel/plugin-syntax-flow@7.18.6(@babel/core@7.27.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0))(typescript@5.4.5) + version: 7.0.1(@babel/plugin-syntax-flow@7.18.6(@babel/core@7.27.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0))(typescript@5.4.5) eslint-plugin-flowtype: specifier: ^8.0.3 version: 8.0.3(@babel/plugin-syntax-flow@7.18.6(@babel/core@7.27.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7))(eslint@10.1.0(jiti@2.4.2)) @@ -123,7 +123,7 @@ importers: version: 2.32.0(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2)) eslint-plugin-jest: specifier: ^29.15.0 - version: 29.15.0(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0))(typescript@5.4.5) + version: 29.15.0(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0))(typescript@5.4.5) eslint-plugin-jsdoc: specifier: ^62.8.0 version: 62.8.0(eslint@10.1.0(jiti@2.4.2)) @@ -135,7 +135,7 @@ importers: version: 7.37.5(eslint@10.1.0(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: next - version: 7.1.0-canary-705268dc-20260409(eslint@10.1.0(jiti@2.4.2)) + version: 7.1.0-canary-56824423-20260414(eslint@10.1.0(jiti@2.4.2)) fast-glob: specifier: 3.2.11 version: 3.2.11 @@ -144,7 +144,7 @@ importers: version: 5.1.11 jest: specifier: ^29.6.4 - version: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + version: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) jsonfile: specifier: ^6.1.0 version: 6.1.0 @@ -183,10 +183,10 @@ importers: version: 3.1.1 vite: specifier: ^6.3.0 - version: 6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@20.19.37)(jsdom@22.1.0)(msw@2.7.5(@types/node@20.19.37)(typescript@5.4.5))(vite@6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 4.1.0(@types/node@22.19.15)(jsdom@22.1.0)(msw@2.7.5(@types/node@22.19.15)(typescript@5.4.5))(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) integration: dependencies: @@ -395,7 +395,7 @@ importers: version: 5.0.1 '@types/node': specifier: ^22.13.1 - version: 22.14.0 + version: 22.19.15 '@types/react': specifier: ^18.0.27 version: 18.2.18 @@ -404,16 +404,16 @@ importers: version: 18.2.7 '@vitejs/plugin-react': specifier: ^4.5.2 - version: 4.5.2(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 4.5.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) typescript: specifier: 'catalog:' version: 5.4.5 vite: specifier: ^6.3.0 - version: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) integration/helpers/rsc-vite-framework: dependencies: @@ -456,7 +456,7 @@ importers: version: 5.0.1 '@types/node': specifier: ^22.13.1 - version: 22.14.0 + version: 22.19.15 '@types/react': specifier: ^18.0.27 version: 18.2.18 @@ -468,13 +468,13 @@ importers: version: 1.19.0(babel-plugin-macros@3.1.0) '@vanilla-extract/vite-plugin': specifier: ^5.2.0 - version: 5.2.0(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0) + version: 5.2.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 6.0.1(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -483,10 +483,10 @@ importers: version: 5.4.5 vite: specifier: ^8.0.0 - version: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) vite-env-only: specifier: ^3.0.1 - version: 3.0.1(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 3.0.1(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) integration/helpers/vite-5-template: dependencies: @@ -1725,7 +1725,7 @@ importers: version: 5.0.1 '@types/node': specifier: ^22.13.1 - version: 22.14.0 + version: 22.19.15 '@types/react': specifier: ^18.0.27 version: 18.2.18 @@ -1734,10 +1734,10 @@ importers: version: 18.2.7 '@vitejs/plugin-react': specifier: ^4.5.2 - version: 4.5.2(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 4.5.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -1746,7 +1746,7 @@ importers: version: 5.4.5 vite: specifier: ^6.3.0 - version: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) playground/rsc-vite-7-framework: dependencies: @@ -1792,7 +1792,7 @@ importers: version: 5.0.1 '@types/node': specifier: ^22.13.1 - version: 22.14.0 + version: 22.19.15 '@types/react': specifier: ^18.0.27 version: 18.2.18 @@ -1801,7 +1801,7 @@ importers: version: 18.2.7 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -1816,7 +1816,7 @@ importers: version: 5.4.5 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) playground/rsc-vite-framework: dependencies: @@ -1862,7 +1862,7 @@ importers: version: 5.0.1 '@types/node': specifier: ^22.13.1 - version: 22.14.0 + version: 22.19.15 '@types/react': specifier: ^18.0.27 version: 18.2.18 @@ -1871,7 +1871,7 @@ importers: version: 18.2.7 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -1886,7 +1886,7 @@ importers: version: 5.4.5 vite: specifier: ^8.0.0 - version: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + version: 8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) playground/split-route-modules: dependencies: @@ -4464,9 +4464,6 @@ packages: '@types/node@20.19.37': resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} - '@types/node@22.14.0': - resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} - '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} @@ -5883,8 +5880,8 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react-hooks@7.1.0-canary-705268dc-20260409: - resolution: {integrity: sha512-1aCvpk6uXAllUERwNHOtcV2e86xO8h+om8fhcpfet0q1Re4exZ1fb/CFblHkw84WFyML76Ry84bysxbDE6Wvpw==} + eslint-plugin-react-hooks@7.1.0-canary-56824423-20260414: + resolution: {integrity: sha512-Wl+dIezUEN+k9EaYiHTmitSl4PogGtn0toJC7jqMHA9PziEWmbwa5D8sF82Wg3nwy7Dcl6XhUFDtZVaHtD97MA==} engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 @@ -10948,14 +10945,6 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inquirer/confirm@5.1.9(@types/node@20.19.37)': - dependencies: - '@inquirer/core': 10.1.10(@types/node@20.19.37) - '@inquirer/type': 3.0.6(@types/node@20.19.37) - optionalDependencies: - '@types/node': 20.19.37 - optional: true - '@inquirer/confirm@5.1.9(@types/node@22.19.15)': dependencies: '@inquirer/core': 10.1.10(@types/node@22.19.15) @@ -10963,20 +10952,6 @@ snapshots: optionalDependencies: '@types/node': 22.19.15 - '@inquirer/core@10.1.10(@types/node@20.19.37)': - dependencies: - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@20.19.37) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 20.19.37 - optional: true - '@inquirer/core@10.1.10(@types/node@22.19.15)': dependencies: '@inquirer/figures': 1.0.11 @@ -10992,11 +10967,6 @@ snapshots: '@inquirer/figures@1.0.11': {} - '@inquirer/type@3.0.6(@types/node@20.19.37)': - optionalDependencies: - '@types/node': 20.19.37 - optional: true - '@inquirer/type@3.0.6(@types/node@22.19.15)': optionalDependencies: '@types/node': 22.19.15 @@ -11023,7 +10993,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -11036,14 +11006,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.8.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -11068,7 +11038,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -11086,7 +11056,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.0.2 - '@types/node': 20.19.37 + '@types/node': 22.19.15 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -11108,7 +11078,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.19.37 + '@types/node': 22.19.15 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -11178,7 +11148,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/yargs': 17.0.24 chalk: 4.1.2 @@ -11692,7 +11662,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/chai@5.2.3': dependencies: @@ -11702,11 +11672,11 @@ snapshots: '@types/compression@1.8.1': dependencies: '@types/express': 4.17.21 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/connect@3.4.38': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/cookie@0.6.0': {} @@ -11714,7 +11684,7 @@ snapshots: '@types/cross-spawn@6.0.6': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/debug@4.1.12': dependencies: @@ -11746,14 +11716,14 @@ snapshots: '@types/express-serve-static-core@4.17.43': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 '@types/express-serve-static-core@5.0.6': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -11774,20 +11744,20 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/glob@8.1.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/graceful-fs@4.1.6': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/gunzip-maybe@1.4.2': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/hast@3.0.4': dependencies: @@ -11812,13 +11782,13 @@ snapshots: '@types/jsdom@20.0.1': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/tough-cookie': 4.0.5 parse5: 7.1.2 '@types/jsdom@21.1.1': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/tough-cookie': 4.0.5 parse5: 7.1.2 @@ -11854,7 +11824,7 @@ snapshots: '@types/morgan@1.9.10': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/ms@0.7.34': {} @@ -11864,10 +11834,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@22.14.0': - dependencies: - undici-types: 6.21.0 - '@types/node@22.19.15': dependencies: undici-types: 6.21.0 @@ -11903,7 +11869,7 @@ snapshots: '@types/recursive-readdir@2.2.4': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/scheduler@0.16.2': {} @@ -11912,22 +11878,22 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/serve-static@1.15.5': dependencies: '@types/http-errors': 2.0.4 '@types/mime': 3.0.4 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/set-cookie-parser@2.4.7': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/shelljs@0.8.16': dependencies: '@types/glob': 7.2.0 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/source-map-support@0.5.10': dependencies: @@ -11941,7 +11907,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/supertest@2.0.16': dependencies: @@ -11949,12 +11915,12 @@ snapshots: '@types/tar-fs@2.0.4': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/tar-stream': 3.1.3 '@types/tar-stream@3.1.3': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/tough-cookie@4.0.5': {} @@ -11964,7 +11930,7 @@ snapshots: '@types/wait-on@5.3.4': dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 '@types/yargs-parser@21.0.0': {} @@ -12163,27 +12129,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vanilla-extract/compiler@0.5.0(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)': - dependencies: - '@vanilla-extract/css': 1.19.0(babel-plugin-macros@3.1.0) - '@vanilla-extract/integration': 8.0.8(babel-plugin-macros@3.1.0) - vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - '@vanilla-extract/compiler@0.5.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)': dependencies: '@vanilla-extract/css': 1.19.0(babel-plugin-macros@3.1.0) @@ -12240,26 +12185,6 @@ snapshots: '@vanilla-extract/private@1.0.9': {} - '@vanilla-extract/vite-plugin@5.2.0(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0)': - dependencies: - '@vanilla-extract/compiler': 0.5.0(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - '@vanilla-extract/integration': 8.0.8(babel-plugin-macros@3.1.0) - vite: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - '@vanilla-extract/vite-plugin@5.2.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(vite@5.1.3(@types/node@22.19.15)(lightningcss@1.32.0)(terser@5.44.1))(yaml@2.8.0)': dependencies: '@vanilla-extract/compiler': 0.5.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) @@ -12340,18 +12265,6 @@ snapshots: - tsx - yaml - '@vitejs/plugin-react@4.5.2(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': - dependencies: - '@babel/core': 7.27.7 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.7) - '@rolldown/pluginutils': 1.0.0-beta.11 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - supports-color - '@vitejs/plugin-react@4.5.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@babel/core': 7.27.7 @@ -12364,12 +12277,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@6.0.1(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': + '@vitejs/plugin-react@6.0.1(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vite: 8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': + '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -12381,12 +12294,12 @@ snapshots: srvx: 0.11.12 strip-literal: 3.1.0 turbo-stream: 3.1.0 - vite: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitefu: 1.1.2(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + vite: 8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vitefu: 1.1.2(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) optionalDependencies: react-server-dom-webpack: 19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)) - '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': + '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -12398,12 +12311,12 @@ snapshots: srvx: 0.11.12 strip-literal: 3.1.0 turbo-stream: 3.1.0 - vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitefu: 1.1.2(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) optionalDependencies: react-server-dom-webpack: 19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0) - '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': + '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0))(react@19.2.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -12415,8 +12328,8 @@ snapshots: srvx: 0.11.12 strip-literal: 3.1.0 turbo-stream: 3.1.0 - vite: 7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitefu: 1.1.2(vite@7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + vite: 7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vitefu: 1.1.2(vite@7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) optionalDependencies: react-server-dom-webpack: 19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0) @@ -12446,14 +12359,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.0(msw@2.7.5(@types/node@20.19.37)(typescript@5.4.5))(vite@6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': + '@vitest/mocker@4.1.0(msw@2.7.5(@types/node@22.19.15)(typescript@5.4.5))(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.7.5(@types/node@20.19.37)(typescript@5.4.5) - vite: 6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + msw: 2.7.5(@types/node@22.19.15)(typescript@5.4.5) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) '@vitest/pretty-format@4.1.0': dependencies: @@ -13227,13 +13140,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0): + create-jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -13738,7 +13651,7 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.18.6(@babel/core@7.27.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0))(typescript@5.4.5): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.18.6(@babel/core@7.27.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0))(typescript@5.4.5): dependencies: '@babel/core': 7.27.7 '@babel/eslint-parser': 7.24.1(@babel/core@7.27.7)(eslint@10.1.0(jiti@2.4.2)) @@ -13750,7 +13663,7 @@ snapshots: eslint: 10.1.0(jiti@2.4.2) eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.18.6(@babel/core@7.27.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7))(eslint@10.1.0(jiti@2.4.2)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2)) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0))(typescript@5.4.5) + eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0))(typescript@5.4.5) eslint-plugin-jsx-a11y: 6.10.2(eslint@10.1.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@10.1.0(jiti@2.4.2)) eslint-plugin-react-hooks: 4.6.2(eslint@10.1.0(jiti@2.4.2)) @@ -13859,24 +13772,24 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0))(typescript@5.4.5): + eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0))(typescript@5.4.5): dependencies: '@typescript-eslint/experimental-utils': 5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5) eslint: 10.1.0(jiti@2.4.2) optionalDependencies: '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5) - jest: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + jest: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-jest@29.15.0(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0))(typescript@5.4.5): + eslint-plugin-jest@29.15.0(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0))(typescript@5.4.5): dependencies: '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5) eslint: 10.1.0(jiti@2.4.2) optionalDependencies: '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5))(eslint@10.1.0(jiti@2.4.2))(typescript@5.4.5) - jest: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + jest: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -13924,7 +13837,7 @@ snapshots: dependencies: eslint: 10.1.0(jiti@2.4.2) - eslint-plugin-react-hooks@7.1.0-canary-705268dc-20260409(eslint@10.1.0(jiti@2.4.2)): + eslint-plugin-react-hooks@7.1.0-canary-56824423-20260414(eslint@10.1.0(jiti@2.4.2)): dependencies: '@babel/core': 7.27.7 '@babel/parser': 7.27.7 @@ -14139,7 +14052,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 require-like: 0.1.2 event-target-shim@5.0.1: {} @@ -14955,7 +14868,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3(babel-plugin-macros@3.1.0) @@ -14975,16 +14888,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0): + jest-cli@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0): dependencies: '@jest/core': 29.7.0(babel-plugin-macros@3.1.0) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + create-jest: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -14994,7 +14907,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0): + jest-config@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0): dependencies: '@babel/core': 7.27.7 '@jest/test-sequencer': 29.7.0 @@ -15019,7 +14932,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -15049,7 +14962,7 @@ snapshots: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/jsdom': 20.0.1 - '@types/node': 20.19.37 + '@types/node': 22.19.15 jest-mock: 29.7.0 jest-util: 29.7.0 jsdom: 22.1.0 @@ -15063,7 +14976,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -15073,7 +14986,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 20.19.37 + '@types/node': 22.19.15 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -15112,7 +15025,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -15147,7 +15060,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -15175,7 +15088,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -15221,7 +15134,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -15240,7 +15153,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.37 + '@types/node': 22.19.15 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -15255,17 +15168,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0): + jest@29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0): dependencies: '@jest/core': 29.7.0(babel-plugin-macros@3.1.0) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.19.37)(babel-plugin-macros@3.1.0) + jest-cli: 29.7.0(@types/node@22.19.15)(babel-plugin-macros@3.1.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -16275,32 +16188,6 @@ snapshots: ms@2.1.3: {} - msw@2.7.5(@types/node@20.19.37)(typescript@5.4.5): - dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.9(@types/node@20.19.37) - '@mswjs/interceptors': 0.37.6 - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.5 - graphql: 16.9.0 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - strict-event-emitter: 0.5.1 - type-fest: 4.40.1 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - '@types/node' - optional: true - msw@2.7.5(@types/node@22.19.15)(typescript@5.4.5): dependencies: '@bundled-es-modules/cookie': 2.0.1 @@ -18050,19 +17937,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-env-only@3.0.1(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): - dependencies: - '@babel/core': 7.27.7 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.7 - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 - babel-dead-code-elimination: 1.0.10 - micromatch: 4.0.5 - vite: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - supports-color - vite-env-only@3.0.1(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): dependencies: '@babel/core': 7.27.7 @@ -18097,27 +17971,6 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -18211,23 +18064,6 @@ snapshots: tsx: 4.19.3 yaml: 2.8.0 - vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): - dependencies: - esbuild: 0.25.0 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.8 - rollup: 4.43.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.14.0 - fsevents: 2.3.3 - jiti: 2.4.2 - lightningcss: 1.32.0 - terser: 5.44.1 - tsx: 4.19.3 - yaml: 2.8.0 - vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): dependencies: esbuild: 0.25.0 @@ -18262,7 +18098,7 @@ snapshots: tsx: 4.19.3 yaml: 2.8.0 - vite@7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): + vite@7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.3) @@ -18271,7 +18107,7 @@ snapshots: rollup: 4.43.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 22.19.15 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.32.0 @@ -18296,23 +18132,6 @@ snapshots: tsx: 4.19.3 yaml: 2.8.0 - vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): - dependencies: - '@oxc-project/runtime': 0.115.0 - lightningcss: 1.32.0 - picomatch: 4.0.3 - postcss: 8.5.8 - rolldown: 1.0.0-rc.9 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.14.0 - esbuild: 0.27.4 - fsevents: 2.3.3 - jiti: 2.4.2 - terser: 5.44.1 - tsx: 4.19.3 - yaml: 2.8.0 - vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0): dependencies: '@oxc-project/runtime': 0.115.0 @@ -18334,22 +18153,22 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitefu@1.1.2(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): + vitefu@1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): optionalDependencies: - vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitefu@1.1.2(vite@7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): + vitefu@1.1.2(vite@7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): optionalDependencies: - vite: 7.3.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vite: 7.3.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitefu@1.1.2(vite@8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): + vitefu@1.1.2(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): optionalDependencies: - vite: 8.0.0(@types/node@22.14.0)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vite: 8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) - vitest@4.1.0(@types/node@20.19.37)(jsdom@22.1.0)(msw@2.7.5(@types/node@20.19.37)(typescript@5.4.5))(vite@6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): + vitest@4.1.0(@types/node@22.19.15)(jsdom@22.1.0)(msw@2.7.5(@types/node@22.19.15)(typescript@5.4.5))(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)): dependencies: '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(msw@2.7.5(@types/node@20.19.37)(typescript@5.4.5))(vite@6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) + '@vitest/mocker': 4.1.0(msw@2.7.5(@types/node@22.19.15)(typescript@5.4.5))(vite@6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) '@vitest/pretty-format': 4.1.0 '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -18366,10 +18185,10 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 6.4.1(@types/node@20.19.37)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.4.2)(lightningcss@1.32.0)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.19.37 + '@types/node': 22.19.15 jsdom: 22.1.0 transitivePeerDependencies: - msw From e4837bac0a5c3f39c997914ba6de29bb1b2a2cad Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 14 Apr 2026 14:29:42 -0400 Subject: [PATCH 08/36] Reduce to an h3 in check-pr comment --- scripts/changes/check-pr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/changes/check-pr.ts b/scripts/changes/check-pr.ts index d3c0c095ea..59bb95e35e 100644 --- a/scripts/changes/check-pr.ts +++ b/scripts/changes/check-pr.ts @@ -21,12 +21,12 @@ import { const COMMENT_MARKER = ""; const COMMENT_FOUND = `${COMMENT_MARKER} -## โœ… Change File Found +### โœ… Change File Found A \`.changes\` file has been found in this PR. Thanks!`; const COMMENT_MISSING = `${COMMENT_MARKER} -## ๐Ÿ“ No Change File Found +### ๐Ÿ“ No Change File Found This PR doesn't include a change file which is used for automated release notes. If your change affects users, please add one (or more) and commit the generated file(s). From c3ac5ddb245fc274ffcaed3eebade0efd8ac0333 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 14 Apr 2026 15:40:47 -0400 Subject: [PATCH 09/36] Re-enable experimental releases --- .github/workflows/release.yml | 60 ++++++++++++- GOVERNANCE.md | 4 +- package.json | 2 + scripts/experimental.ts | 151 +++++++++++++++++++++++++++++++++ scripts/publish.js | 154 ---------------------------------- scripts/version.js | 64 -------------- 6 files changed, 212 insertions(+), 223 deletions(-) create mode 100644 scripts/experimental.ts delete mode 100644 scripts/publish.js delete mode 100644 scripts/version.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 971fb120ee..4e81985163 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,12 @@ +# We use this singular file for all of our releases because we can only specify +# a singular GitHub workflow file in npm's Trusted Publishing configuration. +# See https://docs.npmjs.com/trusted-publishers for more info. +# +# Specific jobs only run on the proper trigger: +# +# - Change file driven stable releases (push to release/hotfix branches) +# - Experimental releases (from a workflow_dispatch trigger) + name: ๐Ÿšข Release/Publish on: @@ -5,6 +14,10 @@ on: branches: - "release" - "hotfix" + workflow_dispatch: + inputs: + branch: + required: true concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -13,7 +26,7 @@ concurrency: jobs: check: name: Check release readiness - if: github.repository == 'remix-run/react-router' + if: github.repository == 'remix-run/react-router' && github.event_name == 'push' runs-on: ubuntu-latest outputs: has_change_files: ${{ steps.check.outputs.has_change_files }} @@ -38,7 +51,7 @@ jobs: pull_request: name: Open pull request needs: check - if: needs.check.outputs.has_change_files == 'true' + if: github.event_name == 'push' && needs.check.outputs.has_change_files == 'true' runs-on: ubuntu-latest permissions: contents: write # enable pushing changes to the origin @@ -69,7 +82,7 @@ jobs: publish: name: Publish needs: check - if: needs.check.outputs.has_change_files == 'false' + if: github.event_name == 'push' && needs.check.outputs.has_change_files == 'false' runs-on: ubuntu-latest permissions: contents: write # enable pushing changes to the origin @@ -97,3 +110,44 @@ jobs: run: pnpm changes:publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + experimental-release: + name: ๐Ÿงช Experimental Release + if: github.repository == 'remix-run/react-router' && github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: write # enable pushing changes to the origin + id-token: write # enable generation of an ID token for publishing + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.branch }} + # checkout using a custom token so that we can push later on + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + + - name: โŽ” Setup node + uses: actions/setup-node@v6 + with: + node-version: 24 # Needed for npm@11 for Trusted Publishing + cache: "pnpm" + + - name: ๐Ÿ“ฅ Install deps + run: pnpm install --frozen-lockfile + + - name: โคด๏ธ Update version + run: | + git config --local user.email "hello@remix.run" + git config --local user.name "Remix Run Bot" + pnpm run experimental:version + git push origin --tags + + - name: ๐Ÿ— Build + run: pnpm build + + - name: ๐Ÿš€ Publish + run: pnpm run experimental:publish diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 738b293c85..c822815056 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -135,7 +135,7 @@ This table gives a high-level overview of the stages, but please see the individ - A proposal enters **Stage 3 โ€” Beta** once it receives **Stage 2 โ€” Alpha** PR approvals from 2 SC members and is merged to `dev` - An SC member authoring the `unstable_` PR counts as an implicit approval, so in those cases explicit approval is required from 1 additional SC member -- This will include the feature in `nightly` releases and the next normal SemVer release for broader beta testing under the `unstable_` flag +- This will include the feature in the next normal SemVer release for broader beta testing behind the `unstable_` flag ### Stage 4 โ€” Stabilization @@ -151,7 +151,7 @@ This table gives a high-level overview of the stages, but please see the individ - A proposal enters **Stage 5 โ€” Stable** once it receives **Stage 4 โ€” Stabilization** PR approvals from at least 50% of the SC members and is merged to `dev` - An SC member authoring the stabilization PR counts as an implicit approval -- This will include the stable feature in `nightly` releases and the next normal SemVer release +- This will include the stable feature in the next normal SemVer release ## Meeting Notes diff --git a/package.json b/package.json index c52c6ee3e6..d61af2ba8b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "docs": "pnpm run docs:typedoc && pnpm run docs:jsdoc", "docs:typedoc": "typedoc", "docs:jsdoc": "node --experimental-strip-types scripts/docs.ts", + "experimental:version": "node ./scripts/experimental.ts version", + "experimental:publish": "node ./scripts/experimental.ts publish", "format": "prettier --ignore-path .prettierignore --write .", "format:check": "prettier --ignore-path .prettierignore --check .", "lint": "eslint --cache .", diff --git a/scripts/experimental.ts b/scripts/experimental.ts new file mode 100644 index 0000000000..5ef44fed42 --- /dev/null +++ b/scripts/experimental.ts @@ -0,0 +1,151 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { colorize, colors } from "./utils/color.ts"; +import { logAndExec } from "./utils/process.ts"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, ".."); + +const packageDirNames = fs + .readdirSync("packages") + .filter((name) => fs.statSync(`packages/${name}`).isDirectory()); + +const command = process.argv[2]; +const args = process.argv.slice(3); +const dryRun = args.includes("--dry-run"); + +if (command === "version") { + await bumpVersion(); +} else if (command === "publish") { + await publishPackages(); +} else { + console.error( + `Usage: node scripts/experimental.ts [version | publish] [--dry-run] `, + ); + process.exit(1); +} + +async function bumpVersion() { + if (!dryRun) { + let status = logAndExec("git status --porcelain", true).trim(); + let lines = status.split("\n"); + invariant( + lines.every((line) => line === "" || line.startsWith("?")), + "Working directory is not clean. Please commit or stash your changes.", + ); + } + + let sha = logAndExec("git rev-parse --short HEAD", true); + invariant(sha != null, "Failed to get git SHA"); + let version = `0.0.0-experimental-${sha}`; + if (dryRun) { + console.log( + colorize( + ` [Dry Run] Would create and switch to branch experimental/${version}\n`, + colors.yellow, + ), + ); + } else { + logAndExec(`git checkout -b experimental/${version}`); + } + + for (let packageDirName of packageDirNames) { + if (dryRun) { + console.log( + colorize( + ` [Dry Run] Would update ${packageDirName} to version ${version}`, + colors.yellow, + ), + ); + } else { + let packageName = updatePackageJson(packageDirName, (pkg) => { + pkg["version"] = version; + }); + console.log( + colorize( + ` Updated ${packageName} to version ${version}`, + colors.green, + ), + ); + } + } + + if (!dryRun) { + logAndExec(`git commit -am "Version ${version}"`); + logAndExec(`git tag -am "Version ${version}" v${version}`); + console.log( + colorize(` Committed and tagged version ${version}`, colors.green), + ); + } +} + +async function publishPackages() { + // Ensure we are in CI. We don't do this manually without --dry-run + invariant( + dryRun || process.env.CI, + `You should always run the publish script from the CI environment!`, + ); + + // Get the current tag, which has the release version number + let version = logAndExec("git tag --list --points-at HEAD", true) + .split("\n") + .find((t) => t.includes("experimental")) + ?.replace(/^v/, ""); + + invariant(version, "Missing experimental version"); + + // Ensure build versions match the release version + for (let packageDirName of packageDirNames) { + let pkgVersion = readPackageJson(packageDirName).version; + invariant( + pkgVersion === version, + `Package ${packageDirName} is on version ${pkgVersion}, but should be on ${version}`, + ); + } + + // 4. Publish to npm + let tag = "experimental"; + let publishCommand = `pnpm publish --recursive --filter "./packages/*" --access public --tag ${tag} --no-git-checks --report-summary`; + if (dryRun) { + console.log( + colorize( + ` [Dry Run] Would publish version ${version} to npm with tag "${tag}" via command:\n` + + ` ${publishCommand}`, + colors.yellow, + ), + ); + } else { + console.log(` Publishing version ${version} to npm with tag "${tag}"`); + logAndExec(publishCommand); + console.log(` Publishing completed`); + } +} + +// --- Utilities --- + +function invariant(cond: unknown, message: string): asserts cond { + if (!cond) throw new Error(message); +} + +function readPackageJson(packageDirName: string): Record { + let file = path.join(rootDir, "packages", packageDirName, "package.json"); + let raw: unknown = JSON.parse(fs.readFileSync(file, "utf-8")); + invariant( + typeof raw === "object" && raw !== null, + `Invalid package.json at ${file}`, + ); + return raw as Record; +} + +function updatePackageJson( + packageDirName: string, + transform: (pkg: Record) => void, +): string | undefined { + let file = path.join(rootDir, "packages", packageDirName, "package.json"); + let pkg = readPackageJson(packageDirName); + transform(pkg); + fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n"); + let name = pkg["name"]; + return typeof name === "string" ? name : undefined; +} diff --git a/scripts/publish.js b/scripts/publish.js deleted file mode 100644 index 40aa7c75db..0000000000 --- a/scripts/publish.js +++ /dev/null @@ -1,154 +0,0 @@ -const path = require("path"); -const { execSync } = require("child_process"); - -const jsonfile = require("jsonfile"); -const semver = require("semver"); - -const rootDir = path.resolve(__dirname, ".."); - -/** - * @param {*} cond - * @param {string} message - * @returns {asserts cond} - */ -function invariant(cond, message) { - if (!cond) throw new Error(message); -} - -/** - * @returns {string} - */ -function getTaggedVersion() { - let output = execSync("git tag --list --points-at HEAD").toString(); - return output.replace(/^v|\n+$/g, ""); -} - -/** - * @param {string} packageName - * @param {string|number} version - */ -async function ensureBuildVersion(packageName, version) { - let file = path.join(rootDir, "packages", packageName, "package.json"); - let json = await jsonfile.readFile(file); - invariant( - json.version === version, - `Package ${packageName} is on version ${json.version}, but should be on ${version}`, - ); -} - -/** - * @param {string} packageName - * @param {string} tag - */ -function publishBuild(packageName, tag, releaseBranch) { - let buildDir = path.join(rootDir, "packages", packageName); - - let args = ["--access public"]; - if (tag) { - args.push(`--tag ${tag}`); - } - - if (tag === "experimental" || tag === "nightly") { - args.push("--no-git-checks"); - } else if (releaseBranch) { - args.push(`--publish-branch ${releaseBranch}`); - } else { - throw new Error( - "Expected a release branch name to be provided for non-experimental/nightly releases", - ); - } - console.log(); - console.log(` pnpm publish ${buildDir} --tag ${tag} --access public`); - console.log(); - execSync(`pnpm publish ${buildDir} ${args.join(" ")}`, { - stdio: "inherit", - }); -} - -/** - * @returns {Promise<1 | 0>} - */ -async function run() { - try { - // 0. Ensure we are in CI. We don't do this manually - invariant( - process.env.CI, - `You should always run the publish script from the CI environment!`, - ); - - // 1. Get the current tag, which has the release version number - let version = getTaggedVersion(); - invariant( - version !== "", - "Missing release version. Run the version script first.", - ); - - // 2. Determine the appropriate npm tag to use - let releaseBranch; - let tag; - if (version.includes("experimental")) { - tag = "experimental"; - } else if (version.includes("nightly")) { - tag = "nightly"; - } else if (version.startsWith("6.")) { - // !!! Note: publish.js is not used for prereleases and stable releases. - // We should be using the Changesets CI process for those. - // These code paths are only left here for emergency usages - releaseBranch = "release-v6"; - tag = null; - } else if (version.startsWith("7.")) { - // !!! Note: publish.js is not used for prereleases and stable releases. - // We should be using the Changesets CI process for those. - // These code paths are only left here for emergency usages - releaseBranch = "release-next"; - tag = semver.prerelease(version) == null ? "latest" : "pre"; - } - - console.log(); - console.log(` Publishing version ${version} to npm with tag "${tag}"`); - - // 3. Ensure build versions match the release version - await ensureBuildVersion("react-router", version); - await ensureBuildVersion("react-router-dom", version); - await ensureBuildVersion("react-router-dev", version); - await ensureBuildVersion("react-router-express", version); - await ensureBuildVersion("react-router-node", version); - await ensureBuildVersion("react-router-serve", version); - await ensureBuildVersion("react-router-architect", version); - await ensureBuildVersion("react-router-cloudflare", version); - await ensureBuildVersion("react-router-fs-routes", version); - await ensureBuildVersion( - "react-router-remix-routes-option-adapter", - version, - ); - await ensureBuildVersion("create-react-router", version); - - // 4. Publish to npm - publishBuild("react-router", tag, releaseBranch); - publishBuild("react-router-dom", tag, releaseBranch); - publishBuild("react-router-dev", tag, releaseBranch); - publishBuild("react-router-express", tag, releaseBranch); - publishBuild("react-router-node", tag, releaseBranch); - publishBuild("react-router-serve", tag, releaseBranch); - publishBuild("react-router-architect", tag, releaseBranch); - publishBuild("react-router-cloudflare", tag, releaseBranch); - publishBuild("react-router-fs-routes", tag, releaseBranch); - publishBuild( - "react-router-remix-routes-option-adapter", - tag, - releaseBranch, - ); - publishBuild("create-react-router", tag, releaseBranch); - } catch (error) { - console.log(); - console.error(` ${error.message}`); - console.log(); - return 1; - } - - return 0; -} - -run().then((code) => { - process.exit(code); -}); diff --git a/scripts/version.js b/scripts/version.js deleted file mode 100644 index 6fc21a9a94..0000000000 --- a/scripts/version.js +++ /dev/null @@ -1,64 +0,0 @@ -const fs = require("node:fs"); -const { execSync } = require("child_process"); -const pc = require("picocolors"); -const semver = require("semver"); - -const { - ensureCleanWorkingDirectory, - invariant, - updatePackageConfig, -} = require("./utils"); - -async function run() { - try { - let args = process.argv.slice(2); - let skipGit = args.includes("--skip-git"); - - let givenVersion = args[0]; - invariant( - givenVersion != null, - `Missing next version. Usage: node version.js [nextVersion]`, - ); - - // 0. Make sure the working directory is clean - if (!skipGit) { - ensureCleanWorkingDirectory(); - } - - // 1. Get the next version number - let version = semver.valid(givenVersion); - invariant(version != null, `Invalid version specifier: ${givenVersion}`); - - // 2. Bump package versions - let packageDirNamesToBump = fs - .readdirSync("packages") - .filter((name) => fs.statSync(`packages/${name}`).isDirectory()); - - for (let packageDirName of packageDirNamesToBump) { - let packageName; - await updatePackageConfig(packageDirName, (pkg) => { - packageName = pkg.name; - pkg.version = version; - }); - console.log(pc.green(` Updated ${packageName} to version ${version}`)); - } - - // 3. Commit and tag - if (!skipGit) { - execSync(`git commit --all --message="Version ${version}"`); - execSync(`git tag -a -m "Version ${version}" v${version}`); - console.log(pc.green(` Committed and tagged version ${version}`)); - } - } catch (error) { - console.log(); - console.error(pc.red(` ${error.message}`)); - console.log(); - return 1; - } - - return 0; -} - -run().then((code) => { - process.exit(code); -}); From bb9433b2e88ef3a902cf1b202d2dfcb0ec323102 Mon Sep 17 00:00:00 2001 From: Nikita <94167692+zeroqs@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:58:36 +0300 Subject: [PATCH 10/36] fix: RouterProviderProps already omits flushSync (#14874) --- contributors.yml | 1 + .../react-router/.changes/patch.fix-dom-router-provider-prop.md | 1 + packages/react-router/lib/dom-export/dom-router-provider.tsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 packages/react-router/.changes/patch.fix-dom-router-provider-prop.md diff --git a/contributors.yml b/contributors.yml index c5f73aa73e..514c1882b4 100644 --- a/contributors.yml +++ b/contributors.yml @@ -486,5 +486,6 @@ - yuri-poliantsev - zeevick10 - zeromask1337 +- zeroqs - zheng-chuang - zxTomw diff --git a/packages/react-router/.changes/patch.fix-dom-router-provider-prop.md b/packages/react-router/.changes/patch.fix-dom-router-provider-prop.md new file mode 100644 index 0000000000..39a423917b --- /dev/null +++ b/packages/react-router/.changes/patch.fix-dom-router-provider-prop.md @@ -0,0 +1 @@ +Remove redundant `Omit` from `react-router/dom` `RouterProvider` diff --git a/packages/react-router/lib/dom-export/dom-router-provider.tsx b/packages/react-router/lib/dom-export/dom-router-provider.tsx index 7a3040aee0..cad38523cf 100644 --- a/packages/react-router/lib/dom-export/dom-router-provider.tsx +++ b/packages/react-router/lib/dom-export/dom-router-provider.tsx @@ -6,6 +6,6 @@ import { RouterProvider as BaseRouterProvider } from "react-router"; export type RouterProviderProps = Omit; -export function RouterProvider(props: Omit) { +export function RouterProvider(props: RouterProviderProps) { return ; } From 142c7030ea61b093c48a816c99989d31c08e50b1 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 14 Apr 2026 16:00:03 -0400 Subject: [PATCH 11/36] Fix fetcher loader redirects from parent middleware (#14974) --- ...tcher-loader-parent-middleware-redirect.md | 1 + .../router/context-middleware-test.tsx | 71 +++++++++++++++++++ packages/react-router/lib/router/router.ts | 10 +++ 3 files changed, 82 insertions(+) create mode 100644 packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md diff --git a/packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md b/packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md new file mode 100644 index 0000000000..ffc89b2e71 --- /dev/null +++ b/packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md @@ -0,0 +1 @@ +Properly handle parent middleware redirects during `fetcher.load` diff --git a/packages/react-router/__tests__/router/context-middleware-test.tsx b/packages/react-router/__tests__/router/context-middleware-test.tsx index b0031e8fea..78e3c64127 100644 --- a/packages/react-router/__tests__/router/context-middleware-test.tsx +++ b/packages/react-router/__tests__/router/context-middleware-test.tsx @@ -1787,6 +1787,77 @@ describe("context/middleware", () => { await router.navigate("/redirect"); expect(router.state.location.pathname).toBe("/target"); }); + + it("allows fetcher.load to follow redirects thrown from parent middleware", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + path: "/parent", + middleware: [ + async () => { + throw redirect("/target"); + }, + ], + children: [ + { + id: "child", + path: "child", + loader() { + return "CHILD"; + }, + }, + ], + }, + { + path: "/target", + }, + ], + }); + + await router.fetch("key", "source", "/parent/child"); + expect(router.state.location.pathname).toBe("/target"); + }); + + it("allows fetcher.submit to follow redirects thrown from parent middleware", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + path: "/parent", + middleware: [ + async () => { + throw redirect("/target"); + }, + ], + children: [ + { + id: "child", + path: "child", + action() { + return "CHILD"; + }, + }, + ], + }, + { + path: "/target", + }, + ], + }); + + await router.fetch("key", "source", "/parent/child", { + formMethod: "POST", + formData: createFormData({}), + }); + expect(router.state.location.pathname).toBe("/target"); + }); }); }); diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index bdd468a6aa..00cf3b79ac 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -2871,6 +2871,16 @@ export function createRouter(init: RouterInit): Router { ); let result = results[match.route.id]; + if (!result) { + // If this error came from a parent middleware before the loader ran, + // then it won't be tied to the fetcher target route + for (let match of matches) { + if (results[match.route.id]) { + result = results[match.route.id]; + break; + } + } + } // We can delete this so long as we weren't aborted by our our own fetcher // re-load which would have put _new_ controller is in fetchControllers if (fetchControllers.get(key) === abortController) { From 29b28e0dae78b9e53383d839df71798c4df6131f Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 15 Apr 2026 15:01:27 -0400 Subject: [PATCH 12/36] Improved types for `generatePath`s `params` arg (#14984) Co-authored-by: harshit-d3v Co-authored-by: Dami Oyeniyi --- ...roved-types-for-generatepaths-param-arg.md | 35 +++++ packages/react-router/lib/hooks.tsx | 7 +- packages/react-router/lib/router/utils.ts | 128 +++++++++++------- 3 files changed, 117 insertions(+), 53 deletions(-) create mode 100644 packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md diff --git a/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md b/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md new file mode 100644 index 0000000000..85f9491b7e --- /dev/null +++ b/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md @@ -0,0 +1,35 @@ +Improved types for `generatePath`'s `param` arg + +Type errors when required params are omitted: + +```ts +// Before +// Passes type checks, but throws at runtime ๐Ÿ’ฅ +generatePath(':required', { required: null }) + +// After +generatePath(':required', { required: null }) +// ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) +``` + +Allow omission of optional params: + +```ts +// Before +generatePath(':optional?', {}) +// ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) + +// After +generatePath(':optional?', {}) +``` + +Allows extra keys: + +```ts +// Before +generatePath(":a", { a: "1", b: "2" }) +// ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) + +// After +generatePath(":a", { a: "1", b: "2" }) +``` \ No newline at end of file diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index e7d917b046..8be7b9f87d 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -185,9 +185,8 @@ export function useNavigationType(): NavigationType { * @returns The path match object if the pattern matches, `null` otherwise */ export function useMatch< - ParamKey extends ParamParseKey, Path extends string, ->(pattern: PathPattern | Path): PathMatch | null { +>(pattern: PathPattern | Path): PathMatch> | null { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the @@ -197,7 +196,7 @@ export function useMatch< let { pathname } = useLocation(); return React.useMemo( - () => matchPath(pattern, decodePath(pathname)), + () => matchPath(pattern, decodePath(pathname)), [pathname, pattern], ); } @@ -667,7 +666,7 @@ export function useParams< > { let { matches } = React.useContext(RouteContext); let routeMatch = matches[matches.length - 1]; - return routeMatch ? (routeMatch.params as any) : {}; + return (routeMatch?.params ?? {}) as any; } /** diff --git a/packages/react-router/lib/router/utils.ts b/packages/react-router/lib/router/utils.ts index 667ec3b1d6..3d462b0528 100644 --- a/packages/react-router/lib/router/utils.ts +++ b/packages/react-router/lib/router/utils.ts @@ -770,49 +770,63 @@ export type RouteManifest = Record; // prettier-ignore type Regex_az = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" -// prettier-ignore -type Regez_AZ = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" +type Regex_AZ = Uppercase; type Regex_09 = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; -type Regex_w = Regex_az | Regez_AZ | Regex_09 | "_"; -type ParamChar = Regex_w | "-"; - -// Emulates regex `+` -type RegexMatchPlus< - CharPattern extends string, - T extends string, -> = T extends `${infer First}${infer Rest}` - ? First extends CharPattern - ? RegexMatchPlus extends never - ? First - : `${First}${RegexMatchPlus}` - : never - : never; - -// Recursive helper for finding path parameters in the absence of wildcards -type _PathParam = - // split path into individual path segments - Path extends `${infer L}/${infer R}` - ? _PathParam | _PathParam - : // find params after `:` - Path extends `:${infer Param}` - ? Param extends `${infer Optional}?${string}` - ? RegexMatchPlus - : RegexMatchPlus - : // otherwise, there aren't any params present - never; - -export type PathParam = +type Regex_w = Regex_az | Regex_AZ | Regex_09 | "_"; + +// prettier-ignore +/** Emulates Regex `+` operator */ +type RegexMatchPlus = + _RegexMatchPlus extends infer result extends string ? + result extends '' ? never : result + : + never + +// prettier-ignore +type _RegexMatchPlus = + T extends `${infer head extends char}${infer rest}` ? + `${head}${_RegexMatchPlus}` + : + '' + +type ParamNameChar = Regex_w | "-"; + +type Simplify = { [K in keyof T]: T[K] } & {}; + +// prettier-ignore +type GeneratePathParams = Simplify< + & ParseParams + & { [key in string]: string | null | undefined } +> + +// prettier-ignore +type ParseParams = // check if path is just a wildcard - Path extends "*" | "/*" - ? "*" - : // look for wildcard at the end of the path - Path extends `${infer Rest}/*` - ? "*" | _PathParam - : // look for params in the absence of wildcards - _PathParam; + path extends '*' ? { '*': string } : + // look for wildcard at the end of the path + path extends `${infer rest}/*` ? { '*': string } & ParseParams : + // look for params in the absence of wildcards + _ParseParams; + +// prettier-ignore +type _ParseParams = + // split path into individual path segments + path extends `${infer left}/${infer right}` ? + _ParseParams & _ParseParams : + // look for optional param in segment + path extends `:${infer param}?${string}` ? + { [key in RegexMatchPlus]?: string | null | undefined } : + // look for required param in segment + path extends `:${infer param}` ? + { [key in RegexMatchPlus]: string } : + {}; + +// prettier-ignore +export type PathParam = (keyof ParseParams) & string; // eslint-disable-next-line @typescript-eslint/no-unused-vars type _tests = [ + // PathParam Expect, "*">>, Expect, "a">>, Expect, "b">>, @@ -821,6 +835,28 @@ type _tests = [ Expect, "a" | "c" | "*">>, Expect, "lang">>, Expect, "lang">>, + + // ParseParams + Expect, { "*": string }>>, + Expect, { a: string }>>, + Expect, { b: string }>>, + Expect, {}>>, + Expect>, { a: string; b: string }>>, + Expect< + Equal< + Simplify>, + { a: string; c: string; "*": string } + > + >, + Expect, { lang: string }>>, + Expect< + Equal, { lang?: string | null | undefined }> + >, + Expect>, { a: string }>>, + Expect>, { a: string }>>, + Expect< + Equal>, { a?: string | null | undefined }> + >, ]; // Attempt to parse the given string segment. If it fails, then just return the @@ -1365,9 +1401,7 @@ function matchRouteBranch< */ export function generatePath( originalPath: Path, - params: { - [key in PathParam]: string | null; - } = {} as any, + params: GeneratePathParams = {} as any, ): string { let path: string = originalPath; if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) { @@ -1394,15 +1428,14 @@ export function generatePath( // only apply the splat if it's the last segment if (isLastSegment && segment === "*") { - const star = "*" as PathParam; // Apply the splat - return stringify(params[star]); + return stringify(params["*" as keyof typeof params]); } const keyMatch = segment.match(/^:([\w-]+)(\??)(.*)/); if (keyMatch) { const [, key, optional, suffix] = keyMatch; - let param = params[key as PathParam]; + let param = params[key as keyof typeof params]; invariant(optional === "?" || param != null, `Missing ":${key}" param`); return encodeURIComponent(stringify(param)) + suffix; } @@ -1477,13 +1510,10 @@ type Mutable = { * @returns A path match object if the pattern matches the pathname, * or `null` if it does not match. */ -export function matchPath< - ParamKey extends ParamParseKey, - Path extends string, ->( +export function matchPath( pattern: PathPattern | Path, pathname: string, -): PathMatch | null { +): PathMatch> | null { if (typeof pattern === "string") { pattern = { path: pattern, caseSensitive: false, end: true }; } From 8b9a55c4a9892ad5f46ba1007a0d22ab1a5e1277 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Wed, 15 Apr 2026 19:02:19 +0000 Subject: [PATCH 13/36] chore: format --- ...h.improved-types-for-generatepaths-param-arg.md | 14 +++++++------- packages/react-router/lib/hooks.tsx | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md b/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md index 85f9491b7e..bf28169053 100644 --- a/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md +++ b/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md @@ -5,10 +5,10 @@ Type errors when required params are omitted: ```ts // Before // Passes type checks, but throws at runtime ๐Ÿ’ฅ -generatePath(':required', { required: null }) +generatePath(":required", { required: null }); // After -generatePath(':required', { required: null }) +generatePath(":required", { required: null }); // ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) ``` @@ -16,20 +16,20 @@ Allow omission of optional params: ```ts // Before -generatePath(':optional?', {}) +generatePath(":optional?", {}); // ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) // After -generatePath(':optional?', {}) +generatePath(":optional?", {}); ``` Allows extra keys: ```ts // Before -generatePath(":a", { a: "1", b: "2" }) +generatePath(":a", { a: "1", b: "2" }); // ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) // After -generatePath(":a", { a: "1", b: "2" }) -``` \ No newline at end of file +generatePath(":a", { a: "1", b: "2" }); +``` diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 8be7b9f87d..21c10d5dd6 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -184,9 +184,9 @@ export function useNavigationType(): NavigationType { * @param pattern The pattern to match against the current {@link Location} * @returns The path match object if the pattern matches, `null` otherwise */ -export function useMatch< - Path extends string, ->(pattern: PathPattern | Path): PathMatch> | null { +export function useMatch( + pattern: PathPattern | Path, +): PathMatch> | null { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the From 03b8b3487e76593c573d2a02879a077205f13624 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Wed, 15 Apr 2026 19:26:27 +0000 Subject: [PATCH 14/36] chore: generate markdown docs from jsdocs --- docs/api/hooks/useMatch.md | 4 ++-- docs/api/utils/generatePath.md | 4 +--- docs/api/utils/matchPath.md | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/api/hooks/useMatch.md b/docs/api/hooks/useMatch.md index dad84bdc87..ef48934833 100644 --- a/docs/api/hooks/useMatch.md +++ b/docs/api/hooks/useMatch.md @@ -29,9 +29,9 @@ This is useful for components that need to know "active" state, e.g. ## Signature ```tsx -function useMatch, Path extends string>( +function useMatch( pattern: PathPattern | Path, -): PathMatch | null +): PathMatch> | null ``` ## Params diff --git a/docs/api/utils/generatePath.md b/docs/api/utils/generatePath.md index 489f76de84..44069b9bf7 100644 --- a/docs/api/utils/generatePath.md +++ b/docs/api/utils/generatePath.md @@ -35,9 +35,7 @@ generatePath("/users/:id", { id: "123" }); // "/users/123" ```tsx function generatePath( originalPath: Path, - params: { - [key in PathParam]: string | null; - } = as any, + params: GeneratePathParams = as any, ): string {} ``` diff --git a/docs/api/utils/matchPath.md b/docs/api/utils/matchPath.md index 13018d6b7b..3996c5c0cc 100644 --- a/docs/api/utils/matchPath.md +++ b/docs/api/utils/matchPath.md @@ -28,10 +28,10 @@ the match. ## Signature ```tsx -function matchPath, Path extends string>( +function matchPath( pattern: PathPattern | Path, pathname: string, -): PathMatch | null +): PathMatch> | null ``` ## Params From 20045ff6677c902b47dd4897819cec5f3038e7a2 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Wed, 15 Apr 2026 16:02:15 -0400 Subject: [PATCH 15/36] Bring release-comments script into the repo (#14985) --- .github/workflows/release-comment-manual.yml | 41 -- .github/workflows/release.yml | 32 +- package.json | 2 +- scripts/release-comments.ts | 383 +++++++++++++++++++ 4 files changed, 414 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/release-comment-manual.yml create mode 100644 scripts/release-comments.ts diff --git a/.github/workflows/release-comment-manual.yml b/.github/workflows/release-comment-manual.yml deleted file mode 100644 index 98513dd4fd..0000000000 --- a/.github/workflows/release-comment-manual.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Used to manually trigger the release comments workflow - -name: ๐Ÿ’ฌ Release Comment -on: - workflow_dispatch: - inputs: - dryRun: - description: "Should this be a dry run? (true/false)" - type: "boolean" - required: true - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CI: true - -jobs: - comment: - name: ๐Ÿ“ Comment on related issues and pull requests - if: github.repository == 'remix-run/react-router' - runs-on: ubuntu-latest - permissions: - issues: write # enable commenting on released issues - pull-requests: write # enable commenting on released pull requests - steps: - - name: โฌ‡๏ธ Checkout repo - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - # IMPORTANT: if you make changes here, also make them in release.yml - - name: ๐Ÿ“ Comment on related issues and pull requests - uses: remix-run/release-comment-action@v0.5.1 - with: - DRY_RUN: ${{ github.event.inputs.dryRun }} - DIRECTORY_TO_CHECK: "./packages" - PACKAGE_NAME: "react-router" - ISSUE_LABELS_TO_REMOVE: "awaiting release" - ISSUE_LABELS_TO_KEEP_OPEN: "๐Ÿ—บ Roadmap" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e81985163..a8748cea4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: pull_request: name: Open pull request needs: check - if: github.event_name == 'push' && needs.check.outputs.has_change_files == 'true' + if: needs.check.outputs.has_change_files == 'true' runs-on: ubuntu-latest permissions: contents: write # enable pushing changes to the origin @@ -82,7 +82,7 @@ jobs: publish: name: Publish needs: check - if: github.event_name == 'push' && needs.check.outputs.has_change_files == 'false' + if: needs.check.outputs.has_change_files == 'false' runs-on: ubuntu-latest permissions: contents: write # enable pushing changes to the origin @@ -111,6 +111,34 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + comment: + name: ๐Ÿ“ Comment on released issues/pull requests + needs: publish + runs-on: ubuntu-latest + permissions: + issues: write # enable commenting on released issues + pull-requests: write # enable commenting on released pull requests + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + + - name: โŽ” Setup node + uses: actions/setup-node@v6 + with: + node-version: 24 # Needed for node TS support + cache: "pnpm" + + - name: ๐Ÿ“ฅ Install deps + run: pnpm install --frozen-lockfile + + - name: ๐Ÿ“ Comment on released issues and pull requests + run: pnpm run release-comments + experimental-release: name: ๐Ÿงช Experimental Release if: github.repository == 'remix-run/react-router' && github.event_name == 'workflow_dispatch' diff --git a/package.json b/package.json index d61af2ba8b..8afbd78863 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lint": "eslint --cache .", "playground": "node ./scripts/playground.js", "pr-preview": "node ./scripts/pr-preview.ts", - "prerelease": "pnpm build", + "release-comments": "node scripts/release-comments.ts", "setup-installable-branch": "node scripts/setup-installable-branch.ts", "test": "jest", "test:inspect": "node --inspect-brk ./node_modules/.bin/jest", diff --git a/scripts/release-comments.ts b/scripts/release-comments.ts new file mode 100644 index 0000000000..9245011f5e --- /dev/null +++ b/scripts/release-comments.ts @@ -0,0 +1,383 @@ +// 1. get all tags sorted by creation date +// 2. get all commits between current and last tag that changed ./packages using `git` +// 3. check if commit is a PR and get the number,title,body using `gh` +// 4. get issues that are linked in the PR using `gh api` +// 5. comment on PRs and issues with the release version using `gh issue comment` and `gh pr comment` +// 6. close issues that are referenced in the PRs using `gh issue close` + +import semver from "semver"; +import { logAndExec } from "./utils/process.ts"; + +let PACKAGE_NAME = "react-router"; // Package name used in git tags: react-router@x.x.x +let DIRECTORY_TO_CHECK = "packages/."; +let GITHUB_REPOSITORY = "remix-run/react-router"; +let PR_LABELS_TO_REMOVE = "awaiting release"; +let ISSUE_LABELS_TO_REMOVE = "awaiting release"; +let ISSUE_LABELS_TO_KEEP_OPEN = "๐Ÿ—บ๏ธRoadmap"; +let DRY_RUN = process.argv.includes("--dry-run"); + +if (DRY_RUN) { + console.log("โš ๏ธ Running in dry-run mode -- no changes will be made"); +} + +let { latest, previous } = findBoundingTags(); + +// Find the git comments between the tags +let gitCommits = getCommits(previous, latest); + +// Find any PRs associated with those commits +let prs = await findMergedPRs(gitCommits, latest); + +let plural = prs.length > 1 ? "s" : ""; +debug( + `> found ${prs.length} merged PR${plural} that changed ${DIRECTORY_TO_CHECK}`, +); + +// Comment on PRs + comment on/close linked issues +for (let pr of prs) { + await commentOnPrAndLinkedIssues(pr, latest); +} + +function findBoundingTags() { + // Determine the tags making up the delta from the prior release to this release + let packageRegex = new RegExp(`^${PACKAGE_NAME}@`); + let stdout = logAndExec( + [ + "git tag", + `-l ${PACKAGE_NAME}@*`, + "--sort -creatordate", + "--format %\\(refname:strip=2\\)", + ].join(" "), + true, + ); + let stableGitTags = stdout + .split("\n") + .map((tag): Tag => ({ raw: tag, clean: tag.replace(packageRegex, "") })); + + let latest = stableGitTags[0]; + let expectedMajor = semver.major(latest.clean); + if (semver.minor(latest.clean) === 0 && semver.patch(latest.clean) === 0) { + expectedMajor -= 1; + } + let previous = stableGitTags.find( + (t) => t.clean !== latest.clean && semver.major(t.clean) === expectedMajor, + ); + invariant( + previous, + `No previous stable release found for prior major version ${expectedMajor}`, + ); + debug(JSON.stringify({ latest, previous })); + + return { previous, latest }; +} + +function getCommits(from: Tag, to: Tag): Array { + let stdout = logAndExec( + [ + "git", + "log", + "--pretty=format:%H", + `${from.raw}...${to.raw}`, + DIRECTORY_TO_CHECK!, + ].join(" "), + true, + ); + + invariant(stdout.trim() !== "", "No commits found between tags"); + + let gitCommits = stdout.split("\n"); + debug(`> commitCount: ${gitCommits.length}`); + return gitCommits; +} + +async function commentOnPrAndLinkedIssues(pr: MergedPR, latest: Tag) { + let prComment = `๐Ÿค– Hello there,\n\nWe just published version \`${latest.clean}\` which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!\n\nThanks!`; + + debug(`\nPR: https://github.com/${GITHUB_REPOSITORY}/pull/${pr.number}`); + + if (DRY_RUN) { + debug(`[dry-run] would comment on PR #${pr.number}`); + } else { + // Comment on PR + logAndExec( + ["gh", "pr", "comment", String(pr.number), "--body", prComment].join(" "), + ); + + // Remove PR labels + logAndExec( + [ + "gh", + "pr", + "edit", + String(pr.number), + "--remove-label", + PR_LABELS_TO_REMOVE, + ].join(" "), + ); + } + + let promises = pr.issues.map((issue) => commentOnIssue(issue, latest)); + + let results = await Promise.allSettled(promises); + let failures = results.filter((result) => result.status === "rejected"); + if (failures.length > 0) { + throw new Error( + `the following commands failed: ${JSON.stringify(failures)}`, + ); + } +} + +async function commentOnIssue(issue: number, latest: Tag) { + let issueComment = `๐Ÿค– Hello there,\n\nWe just published version \`${latest.clean}\` which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!\n\nThanks!`; + + debug(`Issue: https://github.com/${GITHUB_REPOSITORY}/issues/${issue}`); + + let shouldClose = true; + if (ISSUE_LABELS_TO_KEEP_OPEN) { + try { + let labels = getIssueLabels(String(issue)); + console.log("Labels on issue #" + issue + ": " + labels.join(", ")); + shouldClose = !labels.includes(ISSUE_LABELS_TO_KEEP_OPEN); + } catch (err) { + debug(`โš ๏ธ Unable to get labels for issue #${issue}: ${String(err)}`); + } + } + + if (DRY_RUN) { + debug(`[dry-run] would comment on issue #${issue}`); + if (shouldClose) { + debug(`[dry-run] would close issue #${issue}`); + } + debug( + `[dry-run] would remove label "${ISSUE_LABELS_TO_REMOVE}" from issue #${issue}`, + ); + } else { + // Comment on linked issue + logAndExec( + ["gh", "issue", "comment", String(issue), "--body", issueComment].join( + " ", + ), + ); + + // Close linked issue + if (shouldClose) { + logAndExec(["gh", "issue", "close", String(issue)].join(" ")); + } else { + debug( + `Skipping close of issue #${issue} due to "${ISSUE_LABELS_TO_KEEP_OPEN}" label`, + ); + } + + // Remove labels from linked issue + logAndExec( + [ + "gh", + "issue", + "edit", + String(issue), + "--remove-label", + ISSUE_LABELS_TO_REMOVE, + ].join(" "), + ); + } +} + +type MergedPR = { + number: number; + issues: Array; +}; + +type Tag = { + clean: string; + raw: string; +}; + +function getIssuesClosedViaBody(prBody: string): Array { + if (!prBody) return []; + + /** + * This regex matches for one of github's issue references for auto linking an issue to a PR + * as that only happens when the PR is sent to the default branch of the repo + * https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword + */ + let regex = + /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)(:)?\s#([0-9]+)/gi; + + let matches = prBody.match(regex); + if (!matches) return []; + + let issuesMatch = matches.map((match) => { + let [, issueNumber] = match.split(" #"); + return parseInt(issueNumber, 10); + }); + + return issuesMatch; +} + +type PRResult = { number: number; title: string; url: string; body: string }; + +async function findMergedPRs( + commits: Array, + tag: Tag, +): Promise { + let result = await Promise.all( + commits.map(async (commit) => { + let stdout = logAndExec( + [ + "gh", + "pr", + "list", + "--search", + commit, + "--state", + "merged", + "--json", + "number,title,url,body", + ].join(" "), + true, + ); + + let parsed = JSON.parse(stdout); + + if (parsed.length === 0) return; + + let pr = (parsed[0] as PRResult) ?? null; + + if (!pr) return; + + if (pr.title.includes(`Release ${tag.clean}`)) { + debug(`skipping release PR ${pr.number}`); + return; + } + + let linkedIssues = getIssuesLinkedToPullRequest(pr.url); + let issuesClosedViaBody = getIssuesClosedViaBody(pr.body); + + debug( + JSON.stringify({ pr: pr.number, linkedIssues, issuesClosedViaBody }), + ); + + let uniqueIssues = new Set([...linkedIssues, ...issuesClosedViaBody]); + + return { + number: pr.number, + issues: [...uniqueIssues], + }; + }), + ); + + return result.filter((pr: any): pr is MergedPR => pr != undefined); +} + +type ReferencedIssueResult = { + data: { + resource: { closingIssuesReferences: { nodes: Array<{ number: number }> } }; + }; +}; + +function getIssuesLinkedToPullRequest(prHtmlUrl: string): Array { + let gql = String.raw; + + let query = gql` + query ($prHtmlUrl: URI!, $endCursor: String) { + resource(url: $prHtmlUrl) { + ... on PullRequest { + closingIssuesReferences(first: 100, after: $endCursor) { + nodes { + number + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + } + `; + + let stdout = logAndExec( + [ + "gh api graphql", + "--paginate", + `--field prHtmlUrl=${prHtmlUrl}`, + `--raw-field query='${trimNewlines(query)}'`, + ].join(" "), + true, + ); + + debug(stdout); + + let parsed = JSON.parse(stdout); + + return ( + parsed as ReferencedIssueResult + ).data.resource.closingIssuesReferences.nodes.map((node) => node.number); +} + +type IssueLabelsResult = { + data: { + repository: { issue: { labels: { nodes: Array<{ name: string }> } } }; + }; +}; + +function getIssueLabels(number: string): Array { + let gql = String.raw; + + let query = gql` + query ($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + number + title + url + labels(first: 25) { + nodes { + name + } + } + } + } + } + `; + + let [owner, repo] = GITHUB_REPOSITORY.split("/"); + let stdout = logAndExec( + [ + "gh api graphql", + `--field owner=${owner}`, + `--field repo=${repo}`, + `--field number=${number}`, + `--raw-field query='${trimNewlines(query)}'`, + ].join(" "), + true, + ); + + debug(stdout); + + let parsed = JSON.parse(stdout); + + return (parsed as IssueLabelsResult).data.repository.issue.labels.nodes.map( + (node) => node.name, + ); +} + +function trimNewlines(str: string) { + return str.replace(/[\n\r]+/g, "").replace(/ +/g, " "); +} + +function debug(message: string) { + console.debug(message); +} + +export function invariant(value: boolean, message?: string): asserts value; +export function invariant( + value: T | null | undefined, + message?: string, +): asserts value is T; +export function invariant(value: any, message?: string) { + if (value === false || value === null || typeof value === "undefined") { + console.warn("Test invariant failed:", message); + throw new Error(message); + } +} From 1a9f356e70f780853ba778ce4a2fb73dd6e85845 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Wed, 15 Apr 2026 17:09:24 -0400 Subject: [PATCH 16/36] Remove stop worods from change file slugs --- scripts/changes/add.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/changes/add.ts b/scripts/changes/add.ts index 3f40580ddb..6df50e710c 100644 --- a/scripts/changes/add.ts +++ b/scripts/changes/add.ts @@ -121,9 +121,24 @@ function getPackages(): Package[] { }); } +// Common English stop words that add no meaning to a filename slug +const STOP_WORDS = new Set([ + "a", "an", "the", + "and", "or", "but", "so", "nor", "yet", + "in", "on", "at", "by", "for", "to", "of", "from", "with", "into", "onto", + "about", "as", "via", + "is", "are", "was", "were", "be", "been", "being", + "have", "has", "had", + "do", "does", "did", + "it", "its", "this", "that", "these", "those", + "we", "us", "our", "i", "me", "my", "you", "your", + "he", "him", "his", "she", "her", "they", "them", "their", + "now", "then", "also", "just", +]); + /** - * Converts a free-text description into a kebab-case slug of at most 6 words. - * Non-alphanumeric characters (other than spaces) are stripped before slugging. + * Converts a free-text description into a kebab-case slug of at most 6 + * meaningful words. Stop words and non-alphanumeric characters are stripped. */ function toSlug(description: string): string { return ( @@ -132,6 +147,7 @@ function toSlug(description: string): string { .replace(/[^a-z0-9\s]/g, "") .trim() .split(/\s+/) + .filter((w) => !STOP_WORDS.has(w)) .slice(0, 6) .join("-") || "change" ); From 2032c58a902fd6438c7ccadf88916d4d1c044e56 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Wed, 15 Apr 2026 21:10:06 +0000 Subject: [PATCH 17/36] chore: format --- scripts/changes/add.ts | 73 +++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/scripts/changes/add.ts b/scripts/changes/add.ts index 6df50e710c..f82f62290c 100644 --- a/scripts/changes/add.ts +++ b/scripts/changes/add.ts @@ -123,17 +123,68 @@ function getPackages(): Package[] { // Common English stop words that add no meaning to a filename slug const STOP_WORDS = new Set([ - "a", "an", "the", - "and", "or", "but", "so", "nor", "yet", - "in", "on", "at", "by", "for", "to", "of", "from", "with", "into", "onto", - "about", "as", "via", - "is", "are", "was", "were", "be", "been", "being", - "have", "has", "had", - "do", "does", "did", - "it", "its", "this", "that", "these", "those", - "we", "us", "our", "i", "me", "my", "you", "your", - "he", "him", "his", "she", "her", "they", "them", "their", - "now", "then", "also", "just", + "a", + "an", + "the", + "and", + "or", + "but", + "so", + "nor", + "yet", + "in", + "on", + "at", + "by", + "for", + "to", + "of", + "from", + "with", + "into", + "onto", + "about", + "as", + "via", + "is", + "are", + "was", + "were", + "be", + "been", + "being", + "have", + "has", + "had", + "do", + "does", + "did", + "it", + "its", + "this", + "that", + "these", + "those", + "we", + "us", + "our", + "i", + "me", + "my", + "you", + "your", + "he", + "him", + "his", + "she", + "her", + "they", + "them", + "their", + "now", + "then", + "also", + "just", ]); /** From 19413101c59e0f412edd76991ce9464896c75f4a Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Fri, 17 Apr 2026 04:23:54 +0530 Subject: [PATCH 18/36] Fix typegen for pathless layouts with only nested layout children (#14875) * Fix typegen for pathless layouts with only nested layout children Previously, typegen would generate invalid TypeScript when a pathless layout route had only other pathless layout routes as children (no pages). This fix handles the case where pages.size is 0 by generating valid TypeScript. * Sign CLA * minimal test case * changeset -> changes --------- Co-authored-by: Pedro Cattori --- contributors.yml | 1 + integration/typegen-test.ts | 19 +++++++++++++++++++ ...patch.fix-typegen-layouts-without-pages.md | 4 ++++ packages/react-router-dev/typegen/generate.ts | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md diff --git a/contributors.yml b/contributors.yml index 514c1882b4..1c035b6794 100644 --- a/contributors.yml +++ b/contributors.yml @@ -321,6 +321,7 @@ - nowells - Nurai1 - nwleedev +- nyxsky404 - Obi-Dann - okalil - OlegDev1 diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 99bd9a6af9..d50b27bf05 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -868,4 +868,23 @@ test.describe("typegen", () => { }); }); }); + + test("layout without pages", async ({ edit, $ }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig, layout } from "@react-router/dev/routes"; + + export default [ + layout("routes/layout.tsx", []), + ] satisfies RouteConfig; + `, + "app/routes/layout.tsx": tsx` + import { Outlet } from "react-router" + export default function Component() { + return
+ } + `, + }); + await $("pnpm typecheck"); + }); }); diff --git a/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md b/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md new file mode 100644 index 0000000000..41ec16e75f --- /dev/null +++ b/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md @@ -0,0 +1,4 @@ +Fix typegen for layouts without pages + +Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages. +Now, `pages: never;` is correctly generated for those cases. \ No newline at end of file diff --git a/packages/react-router-dev/typegen/generate.ts b/packages/react-router-dev/typegen/generate.ts index 5294375ee4..1b8fcb0098 100644 --- a/packages/react-router-dev/typegen/generate.ts +++ b/packages/react-router-dev/typegen/generate.ts @@ -178,7 +178,7 @@ function routeFilesType({ t.tsPropertySignature( t.identifier("page"), t.tsTypeAnnotation( - pages + pages.size > 0 ? t.tsUnionType( Array.from(pages).map((page) => t.tsLiteralType(t.stringLiteral(page)), From f9fd874939759d51231ff5c8270dce38af3f5169 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Thu, 16 Apr 2026 22:54:36 +0000 Subject: [PATCH 19/36] chore: format --- .../.changes/patch.fix-typegen-layouts-without-pages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md b/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md index 41ec16e75f..138b0900bb 100644 --- a/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md +++ b/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md @@ -1,4 +1,4 @@ Fix typegen for layouts without pages Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages. -Now, `pages: never;` is correctly generated for those cases. \ No newline at end of file +Now, `pages: never;` is correctly generated for those cases. From 59811921d3c7d599077b8cadccdcd65a233165e0 Mon Sep 17 00:00:00 2001 From: Jacob Ebey Date: Mon, 20 Apr 2026 06:59:34 -0700 Subject: [PATCH 20/36] remove the un-documented custom error serialization logic (#14986) --- .../react-router/.changes/patch.error-serialization.md | 1 + .../react-router/__tests__/vendor/turbo-stream-test.ts | 2 +- packages/react-router/vendor/turbo-stream-v2/flatten.ts | 3 --- packages/react-router/vendor/turbo-stream-v2/unflatten.ts | 7 ++----- 4 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 packages/react-router/.changes/patch.error-serialization.md diff --git a/packages/react-router/.changes/patch.error-serialization.md b/packages/react-router/.changes/patch.error-serialization.md new file mode 100644 index 0000000000..14807fe588 --- /dev/null +++ b/packages/react-router/.changes/patch.error-serialization.md @@ -0,0 +1 @@ +Remove the un-documented custom error serialization logic. diff --git a/packages/react-router/__tests__/vendor/turbo-stream-test.ts b/packages/react-router/__tests__/vendor/turbo-stream-test.ts index 008be114f3..f5bfc40884 100644 --- a/packages/react-router/__tests__/vendor/turbo-stream-test.ts +++ b/packages/react-router/__tests__/vendor/turbo-stream-test.ts @@ -185,7 +185,7 @@ test("should encode and decode an Error", async () => { test("should encode and decode an EvalError", async () => { const input = new EvalError("foo"); const output = await quickDecode(encode(input)); - expect(output).toEqual(input); + expect(output).toEqual(new Error("foo")); }); test("should encode and decode array", async () => { diff --git a/packages/react-router/vendor/turbo-stream-v2/flatten.ts b/packages/react-router/vendor/turbo-stream-v2/flatten.ts index 2b75d95d70..14415ccd44 100644 --- a/packages/react-router/vendor/turbo-stream-v2/flatten.ts +++ b/packages/react-router/vendor/turbo-stream-v2/flatten.ts @@ -172,9 +172,6 @@ async function stringify(this: ThisEncode, stack: [unknown, number][]) { deferred[index] = input; } else if (input instanceof Error) { str[index] = `["${TYPE_ERROR}",${JSON.stringify(input.message)}`; - if (input.name !== "Error") { - str[index] += `,${JSON.stringify(input.name)}`; - } str[index] += "]"; } else if (Object.getPrototypeOf(input) === null) { str[index] = `["${TYPE_NULL_OBJECT}",{${partsForObj(input)}}]`; diff --git a/packages/react-router/vendor/turbo-stream-v2/unflatten.ts b/packages/react-router/vendor/turbo-stream-v2/unflatten.ts index 2e686d8657..9c3934a33e 100644 --- a/packages/react-router/vendor/turbo-stream-v2/unflatten.ts +++ b/packages/react-router/vendor/turbo-stream-v2/unflatten.ts @@ -182,11 +182,8 @@ function hydrate(this: ThisDecode, index: number): any { } continue; case TYPE_ERROR: - const [, message, errorType] = value; - let error = - errorType && globalObj && globalObj[errorType] - ? new globalObj[errorType](message) - : new Error(message); + const [, message] = value; + let error = new Error(message); hydrated[index] = error; set(error); continue; From 924883431c0c9e928699480b68999aa7ab17dba6 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Thu, 16 Apr 2026 09:31:22 -0400 Subject: [PATCH 21/36] Add hasOwnProperty to build-time env check --- packages/react-router/lib/server-runtime/dev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-router/lib/server-runtime/dev.ts b/packages/react-router/lib/server-runtime/dev.ts index 3298d37d78..34603c5850 100644 --- a/packages/react-router/lib/server-runtime/dev.ts +++ b/packages/react-router/lib/server-runtime/dev.ts @@ -19,7 +19,7 @@ export function getDevServerHooks(): DevServerHooks | undefined { export function getBuildTimeHeader(request: Request, headerName: string) { if (typeof process !== "undefined") { try { - if (process.env?.IS_RR_BUILD_REQUEST === "yes") { + if (process.env.hasOwnProperty("IS_RR_BUILD_REQUEST") && process.env.IS_RR_BUILD_REQUEST === "yes") { return request.headers.get(headerName); } } catch (e) {} From 184bebebf37b198e2731dcb7313167f3abe224a7 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Mon, 20 Apr 2026 14:02:31 +0000 Subject: [PATCH 22/36] chore: format --- packages/react-router/lib/server-runtime/dev.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-router/lib/server-runtime/dev.ts b/packages/react-router/lib/server-runtime/dev.ts index 34603c5850..1d2b2be036 100644 --- a/packages/react-router/lib/server-runtime/dev.ts +++ b/packages/react-router/lib/server-runtime/dev.ts @@ -19,7 +19,10 @@ export function getDevServerHooks(): DevServerHooks | undefined { export function getBuildTimeHeader(request: Request, headerName: string) { if (typeof process !== "undefined") { try { - if (process.env.hasOwnProperty("IS_RR_BUILD_REQUEST") && process.env.IS_RR_BUILD_REQUEST === "yes") { + if ( + process.env.hasOwnProperty("IS_RR_BUILD_REQUEST") && + process.env.IS_RR_BUILD_REQUEST === "yes" + ) { return request.headers.get(headerName); } } catch (e) {} From cf06394f16c2ca1f0ce7e8118d6d5b4f9277e6a6 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 20 Apr 2026 10:07:56 -0400 Subject: [PATCH 23/36] Update experimental release branch name --- scripts/experimental.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/experimental.ts b/scripts/experimental.ts index 5ef44fed42..ddafdfc3cb 100644 --- a/scripts/experimental.ts +++ b/scripts/experimental.ts @@ -39,15 +39,16 @@ async function bumpVersion() { let sha = logAndExec("git rev-parse --short HEAD", true); invariant(sha != null, "Failed to get git SHA"); let version = `0.0.0-experimental-${sha}`; + let branch = `experimental/${sha}`; if (dryRun) { console.log( colorize( - ` [Dry Run] Would create and switch to branch experimental/${version}\n`, + ` [Dry Run] Would create and switch to branch ${branch}\n`, colors.yellow, ), ); } else { - logAndExec(`git checkout -b experimental/${version}`); + logAndExec(`git checkout -b ${branch}`); } for (let packageDirName of packageDirNames) { @@ -106,7 +107,7 @@ async function publishPackages() { // 4. Publish to npm let tag = "experimental"; - let publishCommand = `pnpm publish --recursive --filter "./packages/*" --access public --tag ${tag} --no-git-checks --report-summary`; + let publishCommand = `pnpm publish --recursive --filter "./packages/*" --access public --tag ${tag} --no-git-checks`; if (dryRun) { console.log( colorize( From 689d760793060c0078fff9e68d0c8b8e1ee7345a Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 20 Apr 2026 10:15:27 -0400 Subject: [PATCH 24/36] Add description to experimental workflow dispatch input --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8748cea4e..76e4153f51 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,7 @@ on: inputs: branch: required: true + description: Experimental release branch concurrency: group: ${{ github.workflow }}-${{ github.ref }} From c6e324b2466295803b5b6ab89cd04a7be2731559 Mon Sep 17 00:00:00 2001 From: Jacob Ebey Date: Mon, 20 Apr 2026 11:39:44 -0700 Subject: [PATCH 25/36] rsc: split route modules (#14965) --- .changeset/three-buses-develop.md | 7 + .../tsconfig.json | 3 +- integration/helpers/playwright-fixture.ts | 20 - .../helpers/rsc-vite-framework/tsconfig.json | 1 + .../helpers/rsc-vite-framework/vite.config.ts | 2 + integration/helpers/vite.ts | 5 +- integration/vite-hmr-hdr-rsc-test.ts | 45 +- integration/vite-hmr-hdr-test.ts | 9 +- .../vite-plugin-order-validation-test.ts | 2 + .../rsc-virtual-route-modules-test.ts | 589 ++++++++++++++ .../react-router-dev/vite/route-chunks.ts | 6 +- packages/react-router-dev/vite/rsc/plugin.ts | 363 +++------ .../vite/rsc/virtual-route-config.ts | 88 ++- .../vite/rsc/virtual-route-modules.ts | 733 ++++++++++++------ .../routes/client-loader-hydrate/route.tsx | 2 +- .../route.tsx | 4 +- .../app/routes/client-loader/route.tsx | 2 +- playground/rsc-vite-framework/package.json | 1 + .../rsc-vite-framework/react-router.config.ts | 3 + playground/rsc-vite-framework/tsconfig.json | 1 + playground/rsc-vite-framework/vite.config.ts | 4 +- pnpm-lock.yaml | 3 + 22 files changed, 1308 insertions(+), 585 deletions(-) create mode 100644 .changeset/three-buses-develop.md create mode 100644 packages/react-router-dev/__tests__/rsc-virtual-route-modules-test.ts diff --git a/.changeset/three-buses-develop.md b/.changeset/three-buses-develop.md new file mode 100644 index 0000000000..1031b967bc --- /dev/null +++ b/.changeset/three-buses-develop.md @@ -0,0 +1,7 @@ +--- +"@react-router/dev": patch +--- + +For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. + +- โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. diff --git a/integration/helpers/cloudflare-dev-proxy-template/tsconfig.json b/integration/helpers/cloudflare-dev-proxy-template/tsconfig.json index 6be050f9c6..8f318fcee7 100644 --- a/integration/helpers/cloudflare-dev-proxy-template/tsconfig.json +++ b/integration/helpers/cloudflare-dev-proxy-template/tsconfig.json @@ -14,6 +14,7 @@ "allowJs": true, "skipLibCheck": true, "baseUrl": ".", - "noEmit": true + "noEmit": true, + "rootDirs": [".", ".react-router/types/"] } } diff --git a/integration/helpers/playwright-fixture.ts b/integration/helpers/playwright-fixture.ts index 9d36e1eb3b..93ee4aa4e1 100644 --- a/integration/helpers/playwright-fixture.ts +++ b/integration/helpers/playwright-fixture.ts @@ -347,26 +347,6 @@ async function doAndWait( } await networkSettledPromise; - // I wish I knew why but Safari seems to get all screwed up without this. - // When you run doAndWait (via clicking a blink or submitting a form) and - // then waitForSelector(). It finds the selector element but thinks it's - // hidden for some unknown reason. It's intermittent, but waiting for the - // next animation frame delaying slightly before the waitForSelector() calls - // seems to fix it ๐Ÿคทโ€โ™‚๏ธ - // - // Test timeout of 30000ms exceeded. - // - // Error: page.waitForSelector: Target closed - // =========================== logs =========================== - // waiting for locator('text=ROOT_BOUNDARY_TEXT') to be visible - // locator resolved to hidden
ROOT_BOUNDARY_TEXT
- // locator resolved to hidden
ROOT_BOUNDARY_TEXT
- // ... and so on until the test times out - let userAgent = await page.evaluate(() => navigator.userAgent); - if (/Safari\//i.test(userAgent) && !/Chrome\//i.test(userAgent)) { - await page.evaluate(() => new Promise((r) => requestAnimationFrame(r))); - } - if (DEBUG) { console.log(`action done, network settled`); } diff --git a/integration/helpers/rsc-vite-framework/tsconfig.json b/integration/helpers/rsc-vite-framework/tsconfig.json index 68287df9cf..3fc8710e47 100644 --- a/integration/helpers/rsc-vite-framework/tsconfig.json +++ b/integration/helpers/rsc-vite-framework/tsconfig.json @@ -1,5 +1,6 @@ { "include": ["**/*.ts", "**/*.tsx", "./.react-router/types/**/*"], + "exclude": ["vite.config*"], "compilerOptions": { "allowImportingTsExtensions": true, "strict": true, diff --git a/integration/helpers/rsc-vite-framework/vite.config.ts b/integration/helpers/rsc-vite-framework/vite.config.ts index 41793b7754..735a60b72d 100644 --- a/integration/helpers/rsc-vite-framework/vite.config.ts +++ b/integration/helpers/rsc-vite-framework/vite.config.ts @@ -1,11 +1,13 @@ import { defineConfig } from "vite"; import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite"; +import react from "@vitejs/plugin-react"; import rsc from "@vitejs/plugin-rsc"; export default defineConfig({ plugins: [ // @ts-ignore reactRouterRSC({ __runningWithinTheReactRouterMonoRepo: true }), + react(), rsc(), ], }); diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index f630968ecb..273fb18496 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -106,6 +106,7 @@ export const viteConfig = { ? "import { reactRouter } from '@react-router/dev/vite';" : [ "import { unstable_reactRouterRSC as reactRouterRSC } from '@react-router/dev/vite';", + "import react from '@vitejs/plugin-react';", "import rsc from '@vitejs/plugin-rsc';", ].join("\n") } @@ -118,10 +119,10 @@ export const viteConfig = { let useNativeTsconfigPaths = parseInt(vite.version.split(".")[0], 10) >= 8; let plugins = [ - ${args.mdx ? "mdx()," : ""} + ${args.mdx ? "{enforce: 'pre', ...mdx()}," : ""} ${args.vanillaExtract ? "vanillaExtractPlugin({ emitCssInSsr: true })," : ""} ${isRsc ? " reactRouterRSC({ __runningWithinTheReactRouterMonoRepo: true })," : "reactRouter(),"} - ${isRsc ? "rsc()," : ""} + ${isRsc ? "react(), rsc()," : ""} envOnlyMacros(), ]; diff --git a/integration/vite-hmr-hdr-rsc-test.ts b/integration/vite-hmr-hdr-rsc-test.ts index 13e7a90464..01f2b5ee00 100644 --- a/integration/vite-hmr-hdr-rsc-test.ts +++ b/integration/vite-hmr-hdr-rsc-test.ts @@ -365,7 +365,7 @@ test.describe("Vite HMR & HDR (RSC)", () => { await expect(clientComponent).toHaveText( "Imported Client Component HMR: 3", ); - await expect(clientButton).toHaveText("Count: 1"); + await expect(clientButton).toBeVisible(); await expect(hdrStatus).toHaveText( "HDR updated: route & direct 2 & indirect 2", ); @@ -373,6 +373,7 @@ test.describe("Vite HMR & HDR (RSC)", () => { expect(page.errors).toEqual([]); // switch from server-first to client route + const waitPromise = page.waitForLoadState("load"); await edit("app/routes/hmr/route.tsx", (contents) => contents .replace( @@ -381,43 +382,11 @@ test.describe("Vite HMR & HDR (RSC)", () => { ) .replace("HMR updated: 3", "Client Route HMR: 0"), ); - await page.waitForLoadState("networkidle"); - await expect(hmrStatus).toHaveText("Client Route HMR: 0"); - // adding/removing client component exports causes an HMR invalidation and a - // page reload. some browsers maintain input state, so we forcibly clear - await input.clear(); - await input.type("client stateful"); - expect(page.errors).toEqual([]); - await edit("app/routes/hmr/route.tsx", (contents) => - contents.replace("Client Route HMR: 0", "Client Route HMR: 1"), - ); - await page.waitForLoadState("networkidle"); - await expect(hmrStatus).toHaveText("Client Route HMR: 1"); - await expect(input).toHaveValue("client stateful"); - expect(page.errors).toEqual([]); - - // switch from client route back to server-first route - await edit("app/routes/hmr/route.tsx", (contents) => - contents - .replace( - "export default function ClientComponent", - "export function ServerComponent", - ) - .replace("Client Route HMR: 1", "Server Route HMR: 0"), - ); - await page.waitForLoadState("networkidle"); - await expect(hmrStatus).toHaveText("Server Route HMR: 0"); - // adding/removing client component exports causes an HMR invalidation and a - // page reload. some browsers maintain input state, so we forcibly clear - await input.clear(); - await input.type("server stateful"); - expect(page.errors).toEqual([]); - await edit("app/routes/hmr/route.tsx", (contents) => - contents.replace("Server Route HMR: 0", "Server Route HMR: 1"), + await waitPromise; + // await page.waitForLoadState("networkidle"); + await expect(page.locator("#index [data-mounted]")).toHaveText( + "Mounted: yes", ); - await page.waitForLoadState("networkidle"); - await expect(hmrStatus).toHaveText("Server Route HMR: 1"); - await expect(input).toHaveValue("server stateful"); - expect(page.errors).toEqual([]); + await expect(hmrStatus).toHaveText("Client Route HMR: 0"); }); }); diff --git a/integration/vite-hmr-hdr-test.ts b/integration/vite-hmr-hdr-test.ts index 0be7670205..067b01b1af 100644 --- a/integration/vite-hmr-hdr-test.ts +++ b/integration/vite-hmr-hdr-test.ts @@ -6,15 +6,14 @@ import dedent from "dedent"; import * as Express from "./helpers/express"; import { test } from "./helpers/fixtures"; import * as Stream from "./helpers/stream"; -import { viteMajorTemplates, getTemplates } from "./helpers/templates"; +import { viteMajorTemplates } from "./helpers/templates"; const tsx = dedent; const mdx = dedent; -const templates = [ - ...viteMajorTemplates, - ...getTemplates(["rsc-vite-framework"]), -]; +const templates = [...viteMajorTemplates]; + +// RSC Framework HMR/HDR behavior is covered in integration/vite-hmr-hdr-rsc-test.ts. templates.forEach((template) => { const isRsc = template.name.startsWith("rsc-"); diff --git a/integration/vite-plugin-order-validation-test.ts b/integration/vite-plugin-order-validation-test.ts index a3121503ca..d8edae05ce 100644 --- a/integration/vite-plugin-order-validation-test.ts +++ b/integration/vite-plugin-order-validation-test.ts @@ -31,12 +31,14 @@ test.describe("Vite plugin order validation", () => { "vite.config.js": dedent` import { defineConfig } from "vite"; import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite"; + import react from "@vitejs/plugin-react"; import rsc from "@vitejs/plugin-rsc"; import mdx from "@mdx-js/rollup"; export default defineConfig({ plugins: [ reactRouterRSC(), + react(), rsc(), mdx(), ], diff --git a/packages/react-router-dev/__tests__/rsc-virtual-route-modules-test.ts b/packages/react-router-dev/__tests__/rsc-virtual-route-modules-test.ts new file mode 100644 index 0000000000..0d46cab2f0 --- /dev/null +++ b/packages/react-router-dev/__tests__/rsc-virtual-route-modules-test.ts @@ -0,0 +1,589 @@ +import * as assert from "node:assert"; +import * as ts from "typescript"; + +import { virtualRouteModulesPlugin } from "../vite/rsc/virtual-route-modules"; + +const plugin = virtualRouteModulesPlugin({ + enforceSplitRouteModules: () => false, + getRouteIdForFile() { + return "test-route-id"; + }, + isRootRouteModule() { + return false; + }, + async transformToJs(code: string, filename: string) { + return await ts.transpile(code, { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.ESNext, + jsx: ts.JsxEmit.ReactJSX, + }); + }, +}); + +const js = String.raw; + +const fullClientModule = js` + import "./side-effect.css"; + import { client } from "./client"; + import { server } from "./server"; + import { shared } from "./shared"; + export function loader() { console.log(server, shared); } + export function action() { console.log(server, shared); } + export function headers() { console.log(server, shared); } + export function clientLoader() { console.log(client, shared); } + export function clientAction() { console.log(client, shared); } + export function links() { console.log(client, shared); } + export function meta() { console.log(client, shared); } + export default function Route() { console.log(client, shared); } + export function Layout() { console.log(client, shared); } + export function ErrorBoundary() { console.log(client, shared); } + export function HydrateFallback() { console.log(client, shared); } +`; + +const fullServerModule = js` + import "./side-effect.css"; + import { client } from "./client"; + import { server } from "./server"; + import { shared } from "./shared"; + export function loader() { console.log(server, shared); } + export function action() { console.log(server, shared); } + export function headers() { console.log(server, shared); } + export function clientLoader() { console.log(client, shared); } + export function clientAction() { console.log(client, shared); } + export function links() { console.log(client, shared); } + export function meta() { console.log(client, shared); } + export function ServerComponent() { console.log(server, shared); } + export function ServerLayout() { console.log(server, shared); } + export function ServerErrorBoundary() { console.log(server, shared); } + export function ServerHydrateFallback() { console.log(server, shared); } +`; + +const mixedModule = js` + import "./side-effect.css"; + import { client } from "./client"; + import { server } from "./server"; + import { shared } from "./shared"; + export function loader() { console.log(server, shared); } + export function action() { console.log(server, shared); } + export function headers() { console.log(server, shared); } + export function clientLoader() { console.log(client, shared); } + export function clientAction() { console.log(client, shared); } + export function links() { console.log(client, shared); } + export function meta() { console.log(client, shared); } + export function ServerComponent() { console.log(server, shared); } + export function Layout() { console.log(client, shared); } + export function ErrorBoundary() { console.log(client, shared); } + export function HydrateFallback() { console.log(client, shared); } +`; + +const unsplittableModule = js` + import "./side-effect.css"; + import { client } from "./client"; + import { server } from "./server"; + import { shared } from "./shared"; + export const test = "test"; + export function loader() { console.log(server, shared); } + export function action() { console.log(server, shared); } + export function headers() { console.log(server, shared); } + export function clientLoader() { console.log(client, shared, test); } + export function clientAction() { console.log(client, shared, test); } + export function links() { console.log(client, shared); } + export function meta() { console.log(client, shared); } + export default function Route() { console.log(client, shared); } + export function Layout() { console.log(client, shared); } + export function ErrorBoundary() { console.log(client, shared); } + export function HydrateFallback() { console.log(client, shared, test); } +`; + +const transform = plugin.transform.bind({ + environment: { name: "rsc" }, +} as any); + +function withSharedChunkHmr(lines: string[]) { + return [ + ...lines, + 'import * as ___EnsureClientRouteModuleForHMR_REACT___ from "react";', + "export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRouteModuleForHMR_REACT___.createElement(___EnsureClientRouteModuleForHMR_REACT___.Fragment, null) }", + "", + ]; +} + +describe("route entry", () => { + describe("client environment", () => { + const transform = plugin.transform.bind({ + environment: { name: "client" }, + } as any); + + it("transforms full client modules", async () => { + const transformed = await transform(fullClientModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import * as React from "react";', + 'export const clientLoader = async (...args) => import("/test.js?client-route-module=clientLoader").then(mod => mod.clientLoader(...args));', + 'export const clientAction = async (...args) => import("/test.js?client-route-module=clientAction").then(mod => mod.clientAction(...args));', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + 'export { default } from "/test.js?client-route-module=shared";', + 'export { Layout } from "/test.js?client-route-module=shared";', + 'export { ErrorBoundary } from "/test.js?client-route-module=shared";', + 'export const HydrateFallback = React.lazy(() => import("/test.js?client-route-module=HydrateFallback").then(mod => ({ default: mod.HydrateFallback })));\n', + ].join("\n"), + ); + }); + + it("transforms full server modules", async () => { + const transformed = await transform(fullServerModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'export const clientLoader = async (...args) => import("/test.js?client-route-module=clientLoader").then(mod => mod.clientLoader(...args));', + 'export const clientAction = async (...args) => import("/test.js?client-route-module=clientAction").then(mod => mod.clientAction(...args));', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";\n', + ].join("\n"), + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform(mixedModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import * as React from "react";', + 'export const clientLoader = async (...args) => import("/test.js?client-route-module=clientLoader").then(mod => mod.clientLoader(...args));', + 'export const clientAction = async (...args) => import("/test.js?client-route-module=clientAction").then(mod => mod.clientAction(...args));', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + 'export { Layout } from "/test.js?client-route-module=shared";', + 'export { ErrorBoundary } from "/test.js?client-route-module=shared";', + 'export const HydrateFallback = React.lazy(() => import("/test.js?client-route-module=HydrateFallback").then(mod => ({ default: mod.HydrateFallback })));\n', + ].join("\n"), + ); + }); + + it("transforms unsplittable modules", async () => { + const transformed = await transform(unsplittableModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import * as React from "react";', + 'export { test } from "/test.js?client-route-module=shared";', + 'export { clientLoader } from "/test.js?client-route-module=shared";', + 'export { clientAction } from "/test.js?client-route-module=shared";', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + 'export { default } from "/test.js?client-route-module=shared";', + 'export { Layout } from "/test.js?client-route-module=shared";', + 'export { ErrorBoundary } from "/test.js?client-route-module=shared";', + 'export const HydrateFallback = React.lazy(() => import("/test.js?client-route-module=shared").then(mod => ({ default: mod.HydrateFallback })));\n', + ].join("\n"), + ); + }); + }); + + describe("server environment", () => { + function withCss(name: string) { + return [ + `import { ${name} as ${name}WithoutCss } from "/test.js?server-route-module=";`, + `export function ${name}(props) {`, + ` return React.createElement(React.Fragment, null,`, + ` import.meta.viteRsc.loadCss(),`, + ` React.createElement(${name}WithoutCss, props),`, + ` );`, + `}`, + ]; + } + + it("transforms full client modules", async () => { + const transformed = await transform(fullClientModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + 'export { loader } from "/test.js?server-route-module=";', + 'export { action } from "/test.js?server-route-module=";', + 'export { headers } from "/test.js?server-route-module=";', + 'export { clientLoader } from "/test.js?client-route-module=clientLoader";', + 'export { clientAction } from "/test.js?client-route-module=clientAction";', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + 'export { default } from "/test.js?client-route-module=shared";', + 'export { Layout } from "/test.js?client-route-module=shared";', + 'export { ErrorBoundary } from "/test.js?client-route-module=shared";', + 'export { HydrateFallback } from "/test.js?client-route-module=HydrateFallback";\n', + ].join("\n"), + ); + }); + + it("transforms full server modules", async () => { + const transformed = await transform(fullServerModule, "/test.js"); + assert.ok(transformed); + + expect(transformed.code).toBe( + [ + 'import * as React from "react";', + 'import { EnsureClientRouteModuleForHMR___ } from "/test.js?client-route-module=shared";', + "", + 'export { loader } from "/test.js?server-route-module=";', + 'export { action } from "/test.js?server-route-module=";', + 'export { headers } from "/test.js?server-route-module=";', + 'export { clientLoader } from "/test.js?client-route-module=clientLoader";', + 'export { clientAction } from "/test.js?client-route-module=clientAction";', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + ...withCss("ServerComponent").slice(0, 4), + " React.createElement(EnsureClientRouteModuleForHMR___, null),", + ...withCss("ServerComponent").slice(4), + ...withCss("ServerLayout").slice(0, 4), + " React.createElement(EnsureClientRouteModuleForHMR___, null),", + ...withCss("ServerLayout").slice(4), + ...withCss("ServerErrorBoundary").slice(0, 4), + " React.createElement(EnsureClientRouteModuleForHMR___, null),", + ...withCss("ServerErrorBoundary").slice(4), + ...withCss("ServerHydrateFallback").slice(0, 4), + " React.createElement(EnsureClientRouteModuleForHMR___, null),", + ...withCss("ServerHydrateFallback").slice(4), + ].join("\n") + "\n", + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform(mixedModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + 'import * as React from "react";', + 'import { EnsureClientRouteModuleForHMR___ } from "/test.js?client-route-module=shared";', + "", + 'export { loader } from "/test.js?server-route-module=";', + 'export { action } from "/test.js?server-route-module=";', + 'export { headers } from "/test.js?server-route-module=";', + 'export { clientLoader } from "/test.js?client-route-module=clientLoader";', + 'export { clientAction } from "/test.js?client-route-module=clientAction";', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + ...withCss("ServerComponent").slice(0, 4), + " React.createElement(EnsureClientRouteModuleForHMR___, null),", + ...withCss("ServerComponent").slice(4), + 'export { Layout } from "/test.js?client-route-module=shared";', + 'export { ErrorBoundary } from "/test.js?client-route-module=shared";', + 'export { HydrateFallback } from "/test.js?client-route-module=HydrateFallback";', + ].join("\n") + "\n", + ); + }); + + it("transforms unsplittable modules", async () => { + const transformed = await transform(unsplittableModule, "/test.js"); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + 'export { test } from "/test.js?server-route-module=";', + 'export { loader } from "/test.js?server-route-module=";', + 'export { action } from "/test.js?server-route-module=";', + 'export { headers } from "/test.js?server-route-module=";', + 'export { clientLoader } from "/test.js?client-route-module=shared";', + 'export { clientAction } from "/test.js?client-route-module=shared";', + 'export { links } from "/test.js?client-route-module=shared";', + 'export { meta } from "/test.js?client-route-module=shared";', + 'export { default } from "/test.js?client-route-module=shared";', + 'export { Layout } from "/test.js?client-route-module=shared";', + 'export { ErrorBoundary } from "/test.js?client-route-module=shared";', + 'export { HydrateFallback } from "/test.js?client-route-module=shared";\n', + ].join("\n"), + ); + }); + }); +}); + +describe("server-route-module", () => { + it("transforms full client modules", async () => { + const transformed = await transform( + fullClientModule, + "/test.js?server-route-module=", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + 'import "./side-effect.css";', + 'import { server } from "./server";', + 'import { shared } from "./shared";', + "export function loader() {\n console.log(server, shared);\n}", + "export function action() {\n console.log(server, shared);\n}", + "export function headers() {\n console.log(server, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms full server modules", async () => { + const transformed = await transform( + fullServerModule, + "/test.js?server-route-module=", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + 'import "./side-effect.css";', + 'import { server } from "./server";', + 'import { shared } from "./shared";', + "export function loader() {\n console.log(server, shared);\n}", + "export function action() {\n console.log(server, shared);\n}", + "export function headers() {\n console.log(server, shared);\n}", + "export function ServerComponent() {\n console.log(server, shared);\n}", + "export function ServerLayout() {\n console.log(server, shared);\n}", + "export function ServerErrorBoundary() {\n console.log(server, shared);\n}", + "export function ServerHydrateFallback() {\n console.log(server, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform( + mixedModule, + "/test.js?server-route-module=", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + 'import "./side-effect.css";', + 'import { server } from "./server";', + 'import { shared } from "./shared";', + "export function loader() {\n console.log(server, shared);\n}", + "export function action() {\n console.log(server, shared);\n}", + "export function headers() {\n console.log(server, shared);\n}", + "export function ServerComponent() {\n console.log(server, shared);\n}", + ].join("\n"), + ); + }); +}); + +describe("client-route-module=shared", () => { + it("transforms full client modules", async () => { + const transformed = await transform( + fullClientModule, + "/test.js?client-route-module=shared", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + withSharedChunkHmr([ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function links() {\n console.log(client, shared);\n}", + "export function meta() {\n console.log(client, shared);\n}", + "export default function Route() {\n console.log(client, shared);\n}", + "export function Layout() {\n console.log(client, shared);\n}", + "export function ErrorBoundary() {\n console.log(client, shared);\n}", + ]).join("\n"), + ); + }); + + it("transforms full server modules", async () => { + const transformed = await transform( + fullServerModule, + "/test.js?client-route-module=shared", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + withSharedChunkHmr([ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function links() {\n console.log(client, shared);\n}", + "export function meta() {\n console.log(client, shared);\n}", + ]).join("\n"), + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform( + mixedModule, + "/test.js?client-route-module=shared", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + withSharedChunkHmr([ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function links() {\n console.log(client, shared);\n}", + "export function meta() {\n console.log(client, shared);\n}", + "export function Layout() {\n console.log(client, shared);\n}", + "export function ErrorBoundary() {\n console.log(client, shared);\n}", + ]).join("\n"), + ); + }); + + it("transforms unsplittable modules", async () => { + const transformed = await transform( + unsplittableModule, + "/test.js?client-route-module=shared", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + withSharedChunkHmr([ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + 'export const test = "test";', + "export function clientLoader() {\n console.log(client, shared, test);\n}", + "export function clientAction() {\n console.log(client, shared, test);\n}", + "export function links() {\n console.log(client, shared);\n}", + "export function meta() {\n console.log(client, shared);\n}", + "export default function Route() {\n console.log(client, shared);\n}", + "export function Layout() {\n console.log(client, shared);\n}", + "export function ErrorBoundary() {\n console.log(client, shared);\n}", + "export function HydrateFallback() {\n console.log(client, shared, test);\n}", + ]).join("\n"), + ); + }); +}); + +describe("client-route-module=clientLoader", () => { + it("transforms full client modules", async () => { + const transformed = await transform( + fullClientModule, + "/test.js?client-route-module=clientLoader", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function clientLoader() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms full server modules", async () => { + const transformed = await transform( + fullServerModule, + "/test.js?client-route-module=clientLoader", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function clientLoader() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform( + mixedModule, + "/test.js?client-route-module=clientLoader", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function clientLoader() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); +}); + +describe("client-route-module=clientAction", () => { + it("transforms full client modules", async () => { + const transformed = await transform( + fullClientModule, + "/test.js?client-route-module=clientAction", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function clientAction() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms full server modules", async () => { + const transformed = await transform( + fullServerModule, + "/test.js?client-route-module=clientAction", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function clientAction() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform( + mixedModule, + "/test.js?client-route-module=clientAction", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function clientAction() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); +}); + +describe("client-route-module=HydrateFallback", () => { + it("transforms full client modules", async () => { + const transformed = await transform( + fullClientModule, + "/test.js?client-route-module=HydrateFallback", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function HydrateFallback() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); + + it("transforms mixed modules", async () => { + const transformed = await transform( + mixedModule, + "/test.js?client-route-module=HydrateFallback", + ); + assert.ok(transformed); + expect(transformed.code).toBe( + [ + '"use client";', + 'import "./side-effect.css";', + 'import { client } from "./client";', + 'import { shared } from "./shared";', + "export function HydrateFallback() {\n console.log(client, shared);\n}", + ].join("\n"), + ); + }); +}); diff --git a/packages/react-router-dev/vite/route-chunks.ts b/packages/react-router-dev/vite/route-chunks.ts index 9fef92936f..070155d46c 100644 --- a/packages/react-router-dev/vite/route-chunks.ts +++ b/packages/react-router-dev/vite/route-chunks.ts @@ -25,7 +25,11 @@ type Dependencies = { exportedVariableDeclarators: Set; }; -function codeToAst(code: string, cache: Cache, cacheKey: string): Babel.File { +export function codeToAst( + code: string, + cache: Cache, + cacheKey: string, +): Babel.File { // We use structuredClone to allow AST mutation without modifying the cache. return structuredClone( getOrSetFromCache(cache, `${cacheKey}::codeToAst`, code, () => diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts index 0481cd70b7..14a15d3544 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -1,7 +1,6 @@ import type * as Vite from "vite"; import { init as initEsModuleLexer } from "es-module-lexer"; import * as Path from "pathe"; -import * as babel from "@babel/core"; import colors from "picocolors"; import { create } from "../virtual-module"; @@ -19,17 +18,13 @@ import { import { defineCompilerOptions, defineOptimizeDepsCompilerOptions, + getVite, preloadVite, } from "../vite"; import { hasDependency } from "../has-dependency"; import { getOptimizeDepsEntries } from "../optimize-deps-entries"; import { createVirtualRouteConfig } from "./virtual-route-config"; -import { - transformVirtualRouteModules, - parseRouteExports, - isVirtualClientRouteModuleId, - CLIENT_NON_COMPONENT_EXPORTS, -} from "./virtual-route-modules"; +import { virtualRouteModulesPlugin } from "./virtual-route-modules"; import { loadDotenv } from "../load-dotenv"; import { validatePluginOrder } from "../plugins/validate-plugin-order"; import { warnOnClientSourceMaps } from "../plugins/warn-on-client-source-maps"; @@ -67,6 +62,79 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { ); } + function isRootRouteModule(id: string): boolean { + return path.normalize(id) === path.normalize(rootRouteFile); + } + + function getRouteIdForFile(file: string): string | undefined { + let normalizedFile = path.normalize(file); + let directMatch = routeIdByFile?.get(normalizedFile); + if (directMatch) { + return directMatch; + } + + return Array.from(routeIdByFile ?? []).find(([routeFile]) => + path.normalize(routeFile).endsWith(normalizedFile), + )?.[1]; + } + + function isMdxRouteModule(filename: string) { + let extension = path.extname(filename).toLowerCase(); + return extension === ".md" || extension === ".mdx"; + } + + function getTransformLanguage( + filename: string, + ): "ts" | "tsx" | "jsx" | undefined { + let extension = path.extname(filename).toLowerCase(); + + switch (extension) { + case ".ts": + case ".cts": + case ".mts": + return "ts"; + case ".tsx": + return "tsx"; + case ".js": + case ".cjs": + case ".mjs": + case ".jsx": + case ".md": + case ".mdx": + return "jsx"; + default: + return undefined; + } + } + + async function transformToJs( + code: string, + filename: string, + ): Promise { + await preloadVite(); + let vite = getVite(); + let lang = getTransformLanguage(filename); + + return ( + "transformWithOxc" in vite && typeof vite.transformWithOxc === "function" + ? await vite.transformWithOxc(code, filename, { + lang, + jsx: { + runtime: "automatic", + development: viteCommand !== "build", + target: "esnext", + }, + }) + : await vite.transformWithEsbuild(code, filename, { + loader: lang, + target: "esnext", + format: "esm", + jsx: "automatic", + jsxDev: viteCommand !== "build", + }) + ).code; + } + return [ { name: "react-router/rsc", @@ -96,8 +164,6 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { if (userConfig.serverBundles) errors.push("serverBundles"); if (userConfig.future?.v8_middleware === false) errors.push("future.v8_middleware: false"); - if (userConfig.future?.v8_splitRouteModules) - errors.push("future.v8_splitRouteModules"); if (userConfig.future?.v8_viteEnvironmentApi === false) errors.push("future.v8_viteEnvironmentApi: false"); if (userConfig.future?.unstable_subResourceIntegrity) @@ -287,36 +353,6 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { }, }, }, - build: { - rollupOptions: { - // Copied from https://github.com/vitejs/vite-plugin-react/blob/c602225271d4acf462ba00f8d6d8a2e42492c5cd/packages/common/warning.ts - onwarn(warning, defaultHandler) { - if ( - warning.code === "MODULE_LEVEL_DIRECTIVE" && - (warning.message.includes("use client") || - warning.message.includes("use server")) - ) { - return; - } - // https://github.com/vitejs/vite/issues/15012 - if ( - warning.code === "SOURCEMAP_ERROR" && - warning.message.includes("resolve original location") && - warning.pos === 0 - ) { - return; - } - if (viteUserConfig.build?.rollupOptions?.onwarn) { - viteUserConfig.build.rollupOptions.onwarn( - warning, - defaultHandler, - ); - } else { - defaultHandler(warning); - } - }, - }, - }, }; }, configResolved(viteConfig) { @@ -487,20 +523,17 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { } }, }, - { - name: "react-router/rsc/virtual-route-modules", - transform(code, id) { - if (!routeIdByFile) return; - return transformVirtualRouteModules({ - code, - id, - viteCommand, - routeIdByFile, - rootRouteFile, - viteEnvironment: this.environment, - }); + virtualRouteModulesPlugin({ + environments: { + client: ["client", "ssr"], + server: ["rsc"], }, - }, + getRouteIdForFile, + isRootRouteModule, + transformToJs, + enforceSplitRouteModules: () => + config.future.v8_splitRouteModules === "enforce", + }), { name: "react-router/rsc/virtual-basename", resolveId(id) { @@ -546,142 +579,22 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { return viteCommand === "serve" ? [ - `import RefreshRuntime from "${virtual.hmrRuntime.id}"`, - "RefreshRuntime.injectIntoGlobalHook(window)", - "window.$RefreshReg$ = () => {}", - "window.$RefreshSig$ = () => (type) => type", - "window.__vite_plugin_react_preamble_installed__ = true", + `if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.on('rsc:update', () => { + // Defer revalidation to the next animation frame so React Fast Refresh + // can apply pending client component updates first. Without this delay, + // the RSC payload (showing updated text) can arrive and be reconciled + // against a DOM that still has the old text, causing a hydration mismatch. + requestAnimationFrame(() => { + __reactRouterDataRouter.revalidate() + }); + }) +}`, ].join("\n") : ""; }, }, - { - name: "react-router/rsc/hmr/runtime", - enforce: "pre", - resolveId(id) { - if (id === virtual.hmrRuntime.id) return virtual.hmrRuntime.resolvedId; - }, - async load(id) { - if (id !== virtual.hmrRuntime.resolvedId) return; - - const reactRefreshDir = path.dirname( - require.resolve("react-refresh/package.json"), - ); - const reactRefreshRuntimePath = join( - reactRefreshDir, - "cjs/react-refresh-runtime.development.js", - ); - - return [ - "const exports = {}", - await readFile(reactRefreshRuntimePath, "utf8"), - await readFile( - require.resolve("./static/rsc-refresh-utils.mjs"), - "utf8", - ), - "export default exports", - ].join("\n"); - }, - }, - { - name: "react-router/rsc/hmr/react-refresh", - async transform(code, id, options) { - if (viteCommand !== "serve") return; - if (id.includes("/node_modules/")) return; - - const filepath = id.split("?")[0]; - const extensionsRE = /\.(jsx?|tsx?|mdx?)$/; - if (!extensionsRE.test(filepath)) return; - - const devRuntime = "react/jsx-dev-runtime"; - const ssr = options?.ssr === true; - const isJSX = filepath.endsWith("x"); - const useFastRefresh = !ssr && (isJSX || code.includes(devRuntime)); - if (!useFastRefresh) return; - - if (isVirtualClientRouteModuleId(id)) { - const routeId = routeIdByFile?.get(filepath); - return { code: addRefreshWrapper({ routeId, code, id }) }; - } - - const result = await babel.transformAsync(code, { - babelrc: false, - configFile: false, - filename: id, - sourceFileName: filepath, - parserOpts: { - sourceType: "module", - allowAwaitOutsideFunction: true, - }, - plugins: [[require("react-refresh/babel"), { skipEnvCheck: true }]], - sourceMaps: true, - }); - if (result === null) return; - - code = result.code!; - const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/; - if (refreshContentRE.test(code)) { - code = addRefreshWrapper({ code, id }); - } - return { code, map: result.map }; - }, - }, - { - name: "react-router/rsc/hmr/updates", - async hotUpdate(this, { server, file, modules }) { - if (this.environment.name !== "rsc") return; - - const clientModules = - server.environments.client.moduleGraph.getModulesByFile(file); - - const vite = await import("vite"); - const isServerOnlyChange = - !clientModules || - clientModules.size === 0 || - // Handle CSS injected from server-first routes (with ?direct query - // string) since the client graph has a reference to the CSS - (vite.isCSSRequest(file) && - Array.from(clientModules).some((mod) => - mod.id?.includes("?direct"), - )); - - for (const mod of getModulesWithImporters(modules)) { - if (!mod.file) continue; - - const normalizedPath = path.normalize(mod.file); - const routeId = routeIdByFile?.get(normalizedPath); - if (routeId !== undefined) { - const routeSource = await readFile(normalizedPath, "utf8"); - const virtualRouteModuleCode = ( - await server.environments.rsc.pluginContainer.transform( - routeSource, - `${normalizedPath}?route-module`, - ) - ).code; - const { staticExports } = parseRouteExports(virtualRouteModuleCode); - const hasAction = staticExports.includes("action"); - const hasComponent = staticExports.includes("default"); - const hasErrorBoundary = staticExports.includes("ErrorBoundary"); - const hasLoader = staticExports.includes("loader"); - - server.hot.send({ - type: "custom", - event: "react-router:hmr", - data: { - routeId, - isServerOnlyChange, - hasAction, - hasComponent, - hasErrorBoundary, - hasLoader, - }, - }); - } - } - - return modules; - }, - }, { name: "react-router/rsc/virtual-react-router-serve-config", resolveId(id) { @@ -864,7 +777,6 @@ const virtual = { routeConfig: create("unstable_rsc/routes"), routeDiscovery: create("unstable_rsc/route-discovery"), injectHmrRuntime: create("unstable_rsc/inject-hmr-runtime"), - hmrRuntime: create("unstable_rsc/runtime"), basename: create("unstable_rsc/basename"), reactRouterServeConfig: create("unstable_rsc/react-router-serve-config"), }; @@ -884,87 +796,6 @@ function getRootDirectory(viteUserConfig: Vite.UserConfig) { return viteUserConfig.root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd(); } -function getModulesWithImporters( - modules: Vite.EnvironmentModuleNode[], -): Set { - const visited = new Set(); - const result = new Set(); - - function walk(module: Vite.EnvironmentModuleNode) { - if (visited.has(module)) return; - - visited.add(module); - result.add(module); - - for (const importer of module.importers) { - walk(importer); - } - } - - for (const module of modules) { - walk(module); - } - - return result; -} - -function addRefreshWrapper({ - routeId, - code, - id, -}: { - routeId?: string; - code: string; - id: string; -}): string { - const acceptExports = - routeId !== undefined ? CLIENT_NON_COMPONENT_EXPORTS : []; - return ( - REACT_REFRESH_HEADER.replaceAll("__SOURCE__", JSON.stringify(id)) + - code + - REACT_REFRESH_FOOTER.replaceAll("__SOURCE__", JSON.stringify(id)) - .replaceAll("__ACCEPT_EXPORTS__", JSON.stringify(acceptExports)) - .replaceAll("__ROUTE_ID__", JSON.stringify(routeId)) - ); -} - -const REACT_REFRESH_HEADER = ` -import RefreshRuntime from "${virtual.hmrRuntime.id}"; - -const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; -let prevRefreshReg; -let prevRefreshSig; - -if (import.meta.hot && !inWebWorker) { - if (!window.__vite_plugin_react_preamble_installed__) { - throw new Error( - "React Router Vite plugin can't detect preamble. Something is wrong." - ); - } - - prevRefreshReg = window.$RefreshReg$; - prevRefreshSig = window.$RefreshSig$; - window.$RefreshReg$ = (type, id) => { - RefreshRuntime.register(type, __SOURCE__ + " " + id) - }; - window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; -}`.replaceAll("\n", ""); // Header is all on one line so source maps aren't affected - -const REACT_REFRESH_FOOTER = ` -if (import.meta.hot && !inWebWorker) { - window.$RefreshReg$ = prevRefreshReg; - window.$RefreshSig$ = prevRefreshSig; - RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => { - RefreshRuntime.registerExportsForReactRefresh(__SOURCE__, currentExports); - import.meta.hot.accept((nextExports) => { - if (!nextExports) return; - __ROUTE_ID__ && window.__reactRouterRouteModuleUpdates.set(__ROUTE_ID__, nextExports); - const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(currentExports, nextExports, __ACCEPT_EXPORTS__); - if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage); - }); - }); -}`; - const getClientBuildDirectory = ( reactRouterConfig: ResolvedReactRouterConfig, ) => path.join(reactRouterConfig.buildDirectory, "client"); diff --git a/packages/react-router-dev/vite/rsc/virtual-route-config.ts b/packages/react-router-dev/vite/rsc/virtual-route-config.ts index 1edddcb042..68fc91431e 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-config.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-config.ts @@ -1,6 +1,8 @@ import path from "pathe"; import type { RouteConfigEntry } from "../../routes"; +const js = String.raw; + export function createVirtualRouteConfig({ appDirectory, routeConfig, @@ -9,7 +11,85 @@ export function createVirtualRouteConfig({ routeConfig: RouteConfigEntry[]; }): { code: string; routeIdByFile: Map } { let routeIdByFile = new Map(); - let code = "export default ["; + let code = js`import * as React from "react"; +function frameworkRoute(lazy) { + return async () => { + const mod = await lazy(); + let Component; + let Layout; + let ErrorBoundary; + let HydrateFallback; + if ("default" in mod && mod.default) { + if ("ServerComponent" in mod && mod.ServerComponent) { + throw new Error("Module cannot have both a default export and a ServerComponent export"); + } + Component = mod.default; + } else if ("ServerComponent" in mod && mod.ServerComponent) { + Component = mod.ServerComponent; + } + if ("Layout" in mod && mod.Layout) { + if ("ServerLayout" in mod && mod.ServerLayout) { + throw new Error("Module cannot have both a Layout export and a ServerLayout export"); + } + Layout = mod.Layout; + } else if ("ServerLayout" in mod && mod.ServerLayout) { + Layout = mod.ServerLayout; + } + if ("ErrorBoundary" in mod && mod.ErrorBoundary) { + if ("ServerErrorBoundary" in mod && mod.ServerErrorBoundary) { + throw new Error( + "Module cannot have both an ErrorBoundary export and a ServerErrorBoundary export", + ); + } + ErrorBoundary = mod.ErrorBoundary; + } else if ("ServerErrorBoundary" in mod && mod.ServerErrorBoundary) { + ErrorBoundary = mod.ServerErrorBoundary; + } + if ("HydrateFallback" in mod && mod.HydrateFallback) { + if ("ServerHydrateFallback" in mod && mod.ServerHydrateFallback) { + throw new Error( + "Module cannot have both a HydrateFallback export and a ServerHydrateFallback export", + ); + } + HydrateFallback = mod.HydrateFallback; + } else if ("ServerHydrateFallback" in mod && mod.ServerHydrateFallback) { + HydrateFallback = mod.ServerHydrateFallback; + } + + const { + action, + clientAction, + clientLoader, + clientMiddleware, + handle, + headers, + links, + loader, + meta, + middleware, + shouldRevalidate, + } = mod; + + return { + Component, + ErrorBoundary, + HydrateFallback, + Layout, + action, + clientAction, + clientLoader, + clientMiddleware, + handle, + headers, + links, + loader, + meta, + middleware, + shouldRevalidate, + }; + }; +} +export default [`; const closeRouteSymbol = Symbol("CLOSE_ROUTE"); let stack: Array = [ @@ -27,9 +107,9 @@ export function createVirtualRouteConfig({ const routeFile = path.resolve(appDirectory, route.file); const routeId = route.id || createRouteId(route.file, appDirectory); routeIdByFile.set(routeFile, routeId); - code += `lazy: () => import(${JSON.stringify( - `${routeFile}?route-module`, - )}),`; + code += `lazy: frameworkRoute(() => import(${JSON.stringify( + `${routeFile}`, + )})),`; code += `id: ${JSON.stringify(routeId)},`; if (typeof route.path === "string") { diff --git a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts index e3c0c566ba..b6382ed55f 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -1,7 +1,420 @@ +import { + init as initEsModuleLexer, + parse as esModuleLexer, +} from "es-module-lexer"; import type * as Vite from "vite"; + import * as babel from "../babel"; -import { parse as esModuleLexer } from "es-module-lexer"; +import type { Cache } from "../cache"; import { removeExports } from "../remove-exports"; +import { + type RouteChunkExportName, + type RouteChunkName, + detectRouteChunks as _detectRouteChunks, +} from "../route-chunks"; + +const ENSURE_CLIENT_ROUTE_MODULE_CHUNK_FOR_HMR = ` +import * as ___EnsureClientRouteModuleForHMR_REACT___ from "react"; +export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRouteModuleForHMR_REACT___.createElement(___EnsureClientRouteModuleForHMR_REACT___.Fragment, null) } +`; + +export function virtualRouteModulesPlugin({ + enforceSplitRouteModules, + environments: { client = ["client", "ssr"], server = ["rsc"] } = {}, + getRouteIdForFile, + isRootRouteModule, + transformToJs, + shouldTransform, +}: { + enforceSplitRouteModules: () => boolean; + environments?: { + client?: string[]; + server?: string[]; + }; + getRouteIdForFile(filename: string): string | undefined; + isRootRouteModule(filename: string): boolean; + order?: "pre" | "post"; + shouldTransform?(filename: string): boolean; + transformToJs: (code: string, filename: string) => Promise; +}) { + let clientEnvironments = new Set(client); + let serverEnvironments = new Set(server); + let cache: Cache = new Map(); + + async function createClientRouteEntry( + id: string, + code: string, + isRootRouteModule: boolean, + routeId: string, + ) { + let result = ""; + + let routeChunks = detectRouteChunks(cache, id, code, isRootRouteModule); + let { staticExports } = await parseRouteExports(code); + + validateRouteModuleExports(staticExports); + + let needsReactImport = false; + for (let exportName of staticExports) { + if (isServerRouteExport(exportName)) { + continue; + } + + if ( + (exportName === "clientAction" || exportName === "clientLoader") && + routeChunks.hasRouteChunkByExportName[ + exportName as RouteChunkExportName + ] + ) { + result += `export const ${exportName} = async (...args) => import("${createId(id, "client-route-module", exportName)}").then(mod => mod.${exportName}(...args));\n`; + } else if (exportName === "HydrateFallback") { + needsReactImport = true; + result += `export const ${exportName} = React.lazy(() => import("${createId( + id, + "client-route-module", + routeChunks.hasRouteChunkByExportName[ + exportName as RouteChunkExportName + ] + ? exportName + : "shared", + )}").then(mod => ({ default: mod.${exportName} })));\n`; + } else { + result += `export { ${exportName} } from "${createId( + id, + "client-route-module", + routeChunks.hasRouteChunkByExportName[ + exportName as RouteChunkExportName + ] + ? exportName + : "shared", + )}";\n`; + } + } + + if (needsReactImport) { + result = `import * as React from "react";\n${result}`; + } + + if (enforceSplitRouteModules() && !isRootRouteModule) { + let { hasRouteChunkByExportName } = routeChunks; + let hasClientAction = staticExports.includes("clientAction"); + let hasClientLoader = staticExports.includes("clientLoader"); + let hasClientMiddleware = staticExports.includes("clientMiddleware"); + let hasHydrateFallback = staticExports.includes("HydrateFallback"); + + validateRouteChunks({ + id: routeId, + valid: { + clientAction: + !hasClientAction || hasRouteChunkByExportName.clientAction, + clientLoader: + !hasClientLoader || hasRouteChunkByExportName.clientLoader, + clientMiddleware: + !hasClientMiddleware || hasRouteChunkByExportName.clientMiddleware, + HydrateFallback: + !hasHydrateFallback || hasRouteChunkByExportName.HydrateFallback, + }, + }); + } + + return { + code: '"use client";\n' + result, + }; + } + + async function createServerRouteEntry( + id: string, + code: string, + isRootRouteModule: boolean, + routeId: string, + ) { + let result = ""; + + let routeChunks = detectRouteChunks(cache, id, code, isRootRouteModule); + let { staticExports } = await parseRouteExports(code); + + validateRouteModuleExports(staticExports); + + let needsReactImport = false; + + for (let exportName of staticExports) { + if (isClientRouteExport(exportName)) { + result += `export { ${exportName} } from "${createId( + id, + "client-route-module", + routeChunks.hasRouteChunkByExportName[ + exportName as RouteChunkExportName + ] + ? exportName + : "shared", + )}";\n`; + } else if (isServerComponentExport(exportName)) { + needsReactImport = true; + result += `import { ${exportName} as ${exportName}WithoutCss } from "${createId(id, "server-route-module")}";\n`; + result += `export function ${exportName}(props) {\n`; + result += ` return React.createElement(React.Fragment, null,\n`; + result += ` import.meta.viteRsc.loadCss(),\n`; + result += ` React.createElement(EnsureClientRouteModuleForHMR___, null),\n`; + result += ` React.createElement(${exportName}WithoutCss, props),\n`; + result += ` );\n`; + result += `}\n`; + } else { + result += `export { ${exportName} } from "${createId(id, "server-route-module")}";\n`; + } + } + + if (needsReactImport) { + result = `import * as React from "react"; +import { EnsureClientRouteModuleForHMR___ } from "${createId(id, "client-route-module", "shared")}";\n +${result}`; + } + + if ( + isRootRouteModule && + !staticExports.includes("ErrorBoundary") && + !staticExports.includes("ServerErrorBoundary") + ) { + result += `export { ErrorBoundary } from "${createId(id, "client-route-module", "shared")}";\n`; + } + + if (enforceSplitRouteModules() && !isRootRouteModule) { + let { hasRouteChunkByExportName } = routeChunks; + let hasClientAction = staticExports.includes("clientAction"); + let hasClientLoader = staticExports.includes("clientLoader"); + let hasClientMiddleware = staticExports.includes("clientMiddleware"); + let hasHydrateFallback = staticExports.includes("HydrateFallback"); + + validateRouteChunks({ + id: routeId, + valid: { + clientAction: + !hasClientAction || hasRouteChunkByExportName.clientAction, + clientLoader: + !hasClientLoader || hasRouteChunkByExportName.clientLoader, + clientMiddleware: + !hasClientMiddleware || hasRouteChunkByExportName.clientMiddleware, + HydrateFallback: + !hasHydrateFallback || hasRouteChunkByExportName.HydrateFallback, + }, + }); + } + + return { + code: result, + }; + } + + function createServerRouteModule(code: string) { + const ast = babel.parse(code, { + sourceType: "module", + }); + removeExports(ast, CLIENT_ROUTE_EXPORTS); + return babel.generate(ast); + } + + async function createClientRouteModuleChunk( + id: string, + code: string, + chunk: "shared" | string, + routeId: string, + isRootRouteModule: boolean, + isDevMode: boolean, + ) { + let routeChunks = detectRouteChunks(cache, id, code, isRootRouteModule); + + const ast = babel.parse(code, { + sourceType: "module", + }); + const { staticExports } = await parseRouteExports(code); + + if (chunk === "shared") { + removeExports(ast, [ + ...SERVER_ROUTE_EXPORTS, + ...routeChunks.chunkedExports, + ]); + } else { + const toRemove = new Set([...SERVER_ROUTE_EXPORTS, ...staticExports]); + toRemove.delete(chunk); + removeExports(ast, Array.from(toRemove)); + } + + const generated = babel.generate(ast); + + let result = '"use client";\n' + generated.code; + + if (chunk === "shared") { + if ( + isRootRouteModule && + !staticExports.includes("ErrorBoundary") && + !staticExports.includes("ServerErrorBoundary") + ) { + const hasRootLayout = + staticExports.includes("Layout") || + staticExports.includes("ServerLayout"); + result += `\nimport { createElement as __rr_createElement } from "react";\n`; + result += `import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";\n`; + result += `export function ErrorBoundary() {\n`; + result += ` return __rr_createElement(UNSAFE_RSCDefaultRootErrorBoundary, { hasRootLayout: ${hasRootLayout} });\n`; + result += `}\n`; + } + + result += ENSURE_CLIENT_ROUTE_MODULE_CHUNK_FOR_HMR; + } + + let hasAction = staticExports.includes("action"); + let hasLoader = staticExports.includes("loader"); + let hasComponent = + staticExports.includes("default") || + staticExports.includes("ServerComponent"); + let hasErrorBoundary = + staticExports.includes("ErrorBoundary") || + staticExports.includes("ServerErrorBoundary"); + + if (isDevMode) { + result += `export function ReactRouterHMRMeta___() {return null;};\n`; + result += `Object.assign(ReactRouterHMRMeta___, { + hasAction: ${JSON.stringify(hasAction)}, + hasComponent: ${JSON.stringify(hasComponent)}, + hasErrorBoundary: ${JSON.stringify(hasErrorBoundary)}, + hasLoader: ${JSON.stringify(hasLoader)}, + hasClientLoader: ${JSON.stringify(staticExports.includes("clientLoader"))}, + });\n`; + result += `\nif (import.meta.hot) {\n`; + result += ` import.meta.hot.accept((mod) => { + if (typeof __reactRouterDataRouter === "object") { + __reactRouterDataRouter._updateRoutesForHMR(new Map([[${JSON.stringify(routeId)}, { + routeModule: mod, + ...mod.ReactRouterHMRMeta___, + }]])); + + if (${chunk === "shared" ? "!mod.default || " : ""}mod.clientLoader || ( + mod.ReactRouterHMRMeta___.hasClientLoader || ReactRouterHMRMeta___.hasClientLoader || ReactRouterHMRMeta___.hasLoader + )) { + __reactRouterDataRouter.revalidate(); + } + } + }); + `; + result += `}\n`; + } + + return { + code: result, + }; + } + + return { + name: "react-router-rsc-virtual-route-modules", + enforce: "pre", + async transform(_code, id) { + const [filename, ...rest] = id.split("?"); + + const routeId = getRouteIdForFile(filename); + + if (!routeId || (shouldTransform && !shouldTransform?.(filename))) { + return; + } + + let isClientEnvironment = clientEnvironments.has(this.environment.name); + let isServerEnvironment = serverEnvironments.has(this.environment.name); + + if (!isClientEnvironment && !isServerEnvironment) { + return; + } + + // this. + let code = await transformToJs(_code, filename); + + let searchParams = + rest.length > 0 ? new URLSearchParams(rest.join("?")) : null; + + let clientRouteModuleType = searchParams?.get("client-route-module"); + let isServerRouteModule = searchParams?.has("server-route-module"); + + if (clientRouteModuleType) { + return await createClientRouteModuleChunk( + id, + code, + clientRouteModuleType, + routeId, + isRootRouteModule(filename), + this.environment.mode === "dev", + ); + } + + if (isServerRouteModule) { + return createServerRouteModule(code); + } + + if (isClientEnvironment) { + return await createClientRouteEntry( + id, + code, + isRootRouteModule(filename), + routeId, + ); + } + + return await createServerRouteEntry( + id, + code, + isRootRouteModule(filename), + routeId, + ); + }, + } satisfies Vite.Plugin; +} + +function createId( + id: string, + type: "client-route-module", + value: string, +): string; +function createId(id: string, type: "server-route-module"): string; +function createId( + id: string, + type: "client-route-module" | "server-route-module", + value?: string, +): string { + let [base, ...rest] = id.split("?"); + const searchParams = new URLSearchParams(rest.join("?")); + searchParams.delete("client-route-module"); + searchParams.delete("server-route-module"); + searchParams.set(type, value || ""); + return `${base}?${searchParams.toString()}`; +} + +export async function parseRouteExports(code: string) { + await initEsModuleLexer; + const [, exportSpecifiers] = esModuleLexer(code); + const staticExports = exportSpecifiers.map(({ n: name }) => name); + return { + staticExports, + hasClientExports: staticExports.some(isClientRouteExport), + }; +} + +export const CLIENT_NON_COMPONENT_EXPORTS = [ + "clientAction", + "clientLoader", + "clientMiddleware", + "handle", + "meta", + "links", + "shouldRevalidate", +] as const; +const CLIENT_ROUTE_EXPORTS = [ + ...CLIENT_NON_COMPONENT_EXPORTS, + "default", + "ErrorBoundary", + "HydrateFallback", + "Layout", +] as const; +type ClientRouteExport = (typeof CLIENT_ROUTE_EXPORTS)[number]; +const CLIENT_ROUTE_EXPORTS_SET = new Set(CLIENT_ROUTE_EXPORTS); +function isClientRouteExport(name: string): name is ClientRouteExport { + return CLIENT_ROUTE_EXPORTS_SET.has(name as ClientRouteExport); +} const SERVER_COMPONENT_EXPORTS = [ "ServerComponent", @@ -29,280 +442,114 @@ function isServerRouteExport(name: string): name is ServerRouteExport { return SERVER_ROUTE_EXPORTS_SET.has(name as ServerRouteExport); } -export const CLIENT_NON_COMPONENT_EXPORTS = [ +const CLIENT_MODULE_CHUNKS = new Set([ "clientAction", "clientLoader", "clientMiddleware", - "handle", - "meta", - "links", - "shouldRevalidate", -] as const; - -const CLIENT_ROUTE_EXPORTS = [ - ...CLIENT_NON_COMPONENT_EXPORTS, - "default", - "ErrorBoundary", "HydrateFallback", - "Layout", -] as const; -type ClientRouteExport = (typeof CLIENT_ROUTE_EXPORTS)[number]; -const CLIENT_ROUTE_EXPORTS_SET = new Set(CLIENT_ROUTE_EXPORTS); -function isClientRouteExport(name: string): name is ClientRouteExport { - return CLIENT_ROUTE_EXPORTS_SET.has(name as ClientRouteExport); -} +]); -const mutuallyExclusiveRouteExports = new Map([ +const MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS = new Map([ ["ErrorBoundary", "ServerErrorBoundary"], ["HydrateFallback", "ServerHydrateFallback"], ["Layout", "ServerLayout"], ["default", "ServerComponent"], ]); -const ROUTE_EXPORTS = [ - ...SERVER_ROUTE_EXPORTS, - ...CLIENT_ROUTE_EXPORTS, -] as const; -type RouteExport = (typeof ROUTE_EXPORTS)[number]; -const ROUTE_EXPORTS_SET = new Set(ROUTE_EXPORTS); -function isRouteExport(name: string): name is RouteExport { - return ROUTE_EXPORTS_SET.has(name as RouteExport); -} -function isCustomRouteExport(name: string) { - return !isRouteExport(name); -} - -function hasReactServerCondition(viteEnvironment: Vite.Environment) { - return viteEnvironment.config.resolve.conditions.includes("react-server"); -} - -type ViteCommand = Vite.ConfigEnv["command"]; - -export function transformVirtualRouteModules({ - id, - code, - viteCommand, - routeIdByFile, - rootRouteFile, - viteEnvironment, -}: { - id: string; - code: string; - viteCommand: ViteCommand; - routeIdByFile: Map; - rootRouteFile: string; - viteEnvironment: Vite.Environment; -}) { - if (isVirtualRouteModuleId(id) || routeIdByFile.has(id)) { - return createVirtualRouteModuleCode({ - id, - code, - rootRouteFile, - viteCommand, - viteEnvironment, - }); - } - - if (isVirtualServerRouteModuleId(id)) { - return createVirtualServerRouteModuleCode({ - id, - code, - viteEnvironment, - }); +function validateRouteModuleExports(toValidate: string[]) { + let errors: [string, string][] = []; + for (let [clientExport, serverExport] of MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS) { + if ( + toValidate.includes(clientExport) && + toValidate.includes(serverExport) + ) { + errors.push([clientExport, serverExport]); + } } - - if (isVirtualClientRouteModuleId(id)) { - return createVirtualClientRouteModuleCode({ - id, - code, - rootRouteFile, - viteCommand, - }); + if (errors.length > 0) { + throw new Error( + `Invalid route module exports. The following pairs of exports are mutually exclusive and cannot be exported from the same module:\n` + + errors + .map( + ([clientExport, serverExport]) => + `- ${clientExport} and ${serverExport}`, + ) + .join("\n"), + ); } } -async function createVirtualRouteModuleCode({ - id, - code: routeSource, - rootRouteFile, - viteCommand, - viteEnvironment, -}: { - id: string; - code: string; - rootRouteFile: string; - viteCommand: ViteCommand; - viteEnvironment: Vite.Environment; -}) { - const isReactServer = hasReactServerCondition(viteEnvironment); - const { staticExports, hasClientExports } = parseRouteExports(routeSource); - - for (const exportName of staticExports) { - if (mutuallyExclusiveRouteExports.has(exportName)) { - const conflictingExport = mutuallyExclusiveRouteExports.get(exportName)!; - if (staticExports.includes(conflictingExport)) { - throw new Error( - `Route module cannot export both "${exportName}" and "${conflictingExport}". Please choose one or the other.`, - ); - } - } +type RouteChunks = ReturnType; + +function detectRouteChunks( + cache: Cache, + id: string, + code: string, + isRootRouteModule: boolean, +): RouteChunks { + function noRouteChunks(): RouteChunks { + return { + chunkedExports: [], + hasRouteChunks: false, + hasRouteChunkByExportName: { + clientAction: false, + clientLoader: false, + clientMiddleware: false, + HydrateFallback: false, + }, + }; } - const clientModuleId = getVirtualClientModuleId(id); - const serverModuleId = getVirtualServerModuleId(id); - - let code = ""; - if (isReactServer && staticExports.some(isServerComponentExport)) { - code += `import React from "react";\n`; - } - for (const staticExport of staticExports) { - if (isReactServer && isServerComponentExport(staticExport)) { - code += `import { ${staticExport} as ${staticExport}WithoutCss } from "${serverModuleId}";\n`; - code += `export ${staticExport === "ServerComponent" ? "default " : " "}function ${staticExport.replace(/^Server/, "")}(props) {\n`; - code += ` return React.createElement(React.Fragment, null,\n`; - code += ` import.meta.viteRsc.loadCss(),\n`; - code += ` React.createElement(${staticExport}WithoutCss, props),\n`; - code += ` );\n`; - code += `}\n`; - } else if (isReactServer && isServerRouteExport(staticExport)) { - code += `export { ${staticExport} } from "${serverModuleId}";\n`; - } else if (isClientRouteExport(staticExport)) { - code += `export { ${staticExport} } from "${clientModuleId}";\n`; - } else if (isCustomRouteExport(staticExport)) { - code += `export { ${staticExport} } from "${isReactServer ? serverModuleId : clientModuleId}";\n`; - } + // If this is the root route, we disable chunking since the chunks would never + // be loaded on demand during navigation. Because the root route is matched + // for all requests, all of its chunks would always be loaded up front during + // the initial page load. Instead of firing off multiple requests to resolve + // the root route code, we want it to be downloaded in a single request. + if (isRootRouteModule) { + return noRouteChunks(); } if ( - isRootRouteFile({ id, rootRouteFile }) && - !staticExports.includes("ErrorBoundary") && - !staticExports.includes("ServerErrorBoundary") + !Array.from(CLIENT_MODULE_CHUNKS).some((exportName) => + code.includes(exportName), + ) ) { - code += `export { ErrorBoundary } from "${clientModuleId}";\n`; - } - - if (viteCommand === "serve" && !hasClientExports) { - code += `export { __ensureClientRouteModuleForHMR } from "${clientModuleId}";\n`; - } - - return code; -} - -function createVirtualServerRouteModuleCode({ - id, - code: routeSource, - viteEnvironment, -}: { - id: string; - code: string; - viteEnvironment: Vite.Environment; -}) { - if (!hasReactServerCondition(viteEnvironment)) { - throw new Error( - [ - "Virtual server route module was loaded outside of the RSC environment.", - `Environment Name: ${viteEnvironment.name}`, - `Module ID: ${id}`, - ].join("\n"), - ); + return noRouteChunks(); } - const { staticExports } = parseRouteExports(routeSource); - const clientModuleId = getVirtualClientModuleId(id); - const serverRouteModuleAst = babel.parse(routeSource, { - sourceType: "module", - }); - removeExports(serverRouteModuleAst, CLIENT_ROUTE_EXPORTS); - - const generatorResult = babel.generate(serverRouteModuleAst); - - for (const staticExport of staticExports) { - if (isClientRouteExport(staticExport)) { - generatorResult.code += "\n"; - generatorResult.code += `export { ${staticExport} } from "${clientModuleId}";\n`; - } - } + let [filename] = id.split("?"); - return generatorResult; + return _detectRouteChunks(code, cache, filename); } -function createVirtualClientRouteModuleCode({ +function validateRouteChunks({ id, - code: routeSource, - rootRouteFile, - viteCommand, + valid, }: { id: string; - code: string; - rootRouteFile: string; - viteCommand: ViteCommand; -}) { - const { staticExports, hasClientExports } = parseRouteExports(routeSource); - - const clientRouteModuleAst = babel.parse(routeSource, { - sourceType: "module", - }); - removeExports(clientRouteModuleAst, SERVER_ROUTE_EXPORTS); - - const generatorResult = babel.generate(clientRouteModuleAst); - generatorResult.code = '"use client";' + generatorResult.code; - - if ( - isRootRouteFile({ id, rootRouteFile }) && - !staticExports.includes("ErrorBoundary") && - !staticExports.includes("ServerErrorBoundary") - ) { - const hasRootLayout = staticExports.includes("Layout"); - generatorResult.code += `\nimport { createElement as __rr_createElement } from "react";\n`; - generatorResult.code += `import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";\n`; - generatorResult.code += `export function ErrorBoundary() {\n`; - generatorResult.code += ` return __rr_createElement(UNSAFE_RSCDefaultRootErrorBoundary, { hasRootLayout: ${hasRootLayout} });\n`; - generatorResult.code += `}\n`; - } - - if (viteCommand === "serve" && !hasClientExports) { - generatorResult.code += `\nexport const __ensureClientRouteModuleForHMR = true;`; + valid: Record, boolean>; +}): void { + let invalidChunks = Object.entries(valid) + .filter(([_, isValid]) => !isValid) + .map(([chunkName]) => chunkName); + + if (invalidChunks.length === 0) { + return; } - return generatorResult; -} - -export function parseRouteExports(code: string) { - const [, exportSpecifiers] = esModuleLexer(code); - const staticExports = exportSpecifiers.map(({ n: name }) => name); - return { - staticExports, - hasClientExports: staticExports.some(isClientRouteExport), - }; -} - -function getVirtualClientModuleId(id: string): string { - return `${id.split("?")[0]}?client-route-module`; -} - -function getVirtualServerModuleId(id: string): string { - return `${id.split("?")[0]}?server-route-module`; -} - -function isVirtualRouteModuleId(id: string): boolean { - return /(\?|&)route-module(&|$)/.test(id); -} + let plural = invalidChunks.length > 1; -export function isVirtualClientRouteModuleId(id: string): boolean { - return /(\?|&)client-route-module(&|$)/.test(id); -} + throw new Error( + [ + `Error splitting route module: ${id}`, -function isVirtualServerRouteModuleId(id: string): boolean { - return /(\?|&)server-route-module(&|$)/.test(id); -} + invalidChunks.map((name) => `- ${name}`).join("\n"), -function isRootRouteFile({ - id, - rootRouteFile, -}: { - id: string; - rootRouteFile: string; -}): boolean { - const filePath = id.split("?")[0]; - return filePath === rootRouteFile; + `${plural ? "These exports" : "This export"} could not be split into ${ + plural ? "their own chunks" : "its own chunk" + } because ${ + plural ? "they share" : "it shares" + } code with other exports. You should extract any shared code into its own module and then import it within the route module.`, + ].join("\n\n"), + ); } diff --git a/playground/rsc-vite-framework/app/routes/client-loader-hydrate/route.tsx b/playground/rsc-vite-framework/app/routes/client-loader-hydrate/route.tsx index 3b29c0fd24..149e6825fb 100644 --- a/playground/rsc-vite-framework/app/routes/client-loader-hydrate/route.tsx +++ b/playground/rsc-vite-framework/app/routes/client-loader-hydrate/route.tsx @@ -17,7 +17,7 @@ export default function ClientLoaderHydrateRoute({ }: Route.ComponentProps) { return (
-

Client loader

+

Client loader!

Loader data: {loaderData}

); diff --git a/playground/rsc-vite-framework/app/routes/client-loader-without-server-loader/route.tsx b/playground/rsc-vite-framework/app/routes/client-loader-without-server-loader/route.tsx index 0a54067815..55dad882fa 100644 --- a/playground/rsc-vite-framework/app/routes/client-loader-without-server-loader/route.tsx +++ b/playground/rsc-vite-framework/app/routes/client-loader-without-server-loader/route.tsx @@ -1,7 +1,7 @@ import type { Route } from "./+types/route"; export function clientLoader() { - return "hello, world from client loader"; + return "hello, world from client loader!"; } export default function ClientLoaderWithoutServerLoaderRoute({ @@ -9,7 +9,7 @@ export default function ClientLoaderWithoutServerLoaderRoute({ }: Route.ComponentProps) { return (
-

Client loader without server loader

+

Client loader without server loader!

Loader data: {loaderData}

); diff --git a/playground/rsc-vite-framework/app/routes/client-loader/route.tsx b/playground/rsc-vite-framework/app/routes/client-loader/route.tsx index e313a3cb77..2fecee23cb 100644 --- a/playground/rsc-vite-framework/app/routes/client-loader/route.tsx +++ b/playground/rsc-vite-framework/app/routes/client-loader/route.tsx @@ -7,7 +7,7 @@ export function loader() { } export function clientLoader() { - return "hello, world from client loader"; + return "hello, world from client loader!"; } export default function ClientLoaderRoute({ diff --git a/playground/rsc-vite-framework/package.json b/playground/rsc-vite-framework/package.json index ac33b6a9af..b210dae97d 100644 --- a/playground/rsc-vite-framework/package.json +++ b/playground/rsc-vite-framework/package.json @@ -21,6 +21,7 @@ "@types/react": "catalog:react-canary", "@types/react-dom": "catalog:react-canary", "@vitejs/plugin-rsc": "catalog:", + "@vitejs/plugin-react": "^6.0.1", "cross-env": "^7.0.3", "remark-frontmatter": "^5.0.0", "remark-mdx-frontmatter": "^5.2.0", diff --git a/playground/rsc-vite-framework/react-router.config.ts b/playground/rsc-vite-framework/react-router.config.ts index 75ea23d722..001a171cee 100644 --- a/playground/rsc-vite-framework/react-router.config.ts +++ b/playground/rsc-vite-framework/react-router.config.ts @@ -3,4 +3,7 @@ import type { Config } from "@react-router/dev/config"; export default { ssr: false, prerender: ["/", "/server-loader"], + future: { + v8_splitRouteModules: "enforce" + } } satisfies Config; diff --git a/playground/rsc-vite-framework/tsconfig.json b/playground/rsc-vite-framework/tsconfig.json index 65ee147911..ad2286bf43 100644 --- a/playground/rsc-vite-framework/tsconfig.json +++ b/playground/rsc-vite-framework/tsconfig.json @@ -1,5 +1,6 @@ { "include": ["**/*.ts", "**/*.tsx", "./.react-router/types/**/*"], + "exclude": ["vite.config.ts"], "compilerOptions": { "allowImportingTsExtensions": true, "strict": true, diff --git a/playground/rsc-vite-framework/vite.config.ts b/playground/rsc-vite-framework/vite.config.ts index dc1a4bfde2..074ab9b8e8 100644 --- a/playground/rsc-vite-framework/vite.config.ts +++ b/playground/rsc-vite-framework/vite.config.ts @@ -4,15 +4,17 @@ import rsc from "@vitejs/plugin-rsc"; import mdx from "@mdx-js/rollup"; import remarkFrontmatter from "remark-frontmatter"; import remarkMdxFrontmatter from "remark-mdx-frontmatter"; +import react from "@vitejs/plugin-react"; export default defineConfig({ build: { minify: false, }, plugins: [ - mdx({ remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter] }), + { enforce: "pre", ...mdx({ remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter] })}, // @ts-ignore reactRouterRSC({ __runningWithinTheReactRouterMonoRepo: true }), + react(), rsc(), ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad674af5e9..8a5822974c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1869,6 +1869,9 @@ importers: '@types/react-dom': specifier: ^18.0.10 version: 18.2.7 + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' version: 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(esbuild@0.27.4)))(react@19.2.3)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.4.2)(terser@5.44.1)(tsx@4.19.3)(yaml@2.8.0)) From ce48dee3fd17667abbb2bec9f23c3b8e4ba007a7 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Mon, 20 Apr 2026 18:40:30 +0000 Subject: [PATCH 26/36] chore: deduplicate `pnpm-lock.yaml` --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a5822974c..ab9fdf7b9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: version: 7.37.5(eslint@10.1.0(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: next - version: 7.1.0-canary-56824423-20260414(eslint@10.1.0(jiti@2.4.2)) + version: 7.1.1-canary-d1727fbf-20260417(eslint@10.1.0(jiti@2.4.2)) fast-glob: specifier: 3.2.11 version: 3.2.11 @@ -5883,8 +5883,8 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react-hooks@7.1.0-canary-56824423-20260414: - resolution: {integrity: sha512-Wl+dIezUEN+k9EaYiHTmitSl4PogGtn0toJC7jqMHA9PziEWmbwa5D8sF82Wg3nwy7Dcl6XhUFDtZVaHtD97MA==} + eslint-plugin-react-hooks@7.1.1-canary-d1727fbf-20260417: + resolution: {integrity: sha512-lGcF2qMJEgVLjyqMqoYUWjMqIqyd09FfXpbw9yVyvsVz6rcPdoPZheTReIiNtC/CaF5YKpGlVhyRw2VnpFwuRQ==} engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 @@ -13840,7 +13840,7 @@ snapshots: dependencies: eslint: 10.1.0(jiti@2.4.2) - eslint-plugin-react-hooks@7.1.0-canary-56824423-20260414(eslint@10.1.0(jiti@2.4.2)): + eslint-plugin-react-hooks@7.1.1-canary-d1727fbf-20260417(eslint@10.1.0(jiti@2.4.2)): dependencies: '@babel/core': 7.27.7 '@babel/parser': 7.27.7 From bc77b329102e18c0acecb839a4357c12a66f06c1 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 20 Apr 2026 16:29:29 -0400 Subject: [PATCH 27/36] Adjust internal error serialization logic (#14992) --- .../.changes/patch.error-serialization.md | 2 +- .../__tests__/vendor/turbo-stream-test.ts | 3 ++- packages/react-router/lib/dom/ssr/single-fetch.tsx | 14 +++++++++++--- .../react-router/vendor/turbo-stream-v2/flatten.ts | 3 +++ .../vendor/turbo-stream-v2/turbo-stream.ts | 2 ++ .../vendor/turbo-stream-v2/unflatten.ts | 12 ++++++++++-- .../react-router/vendor/turbo-stream-v2/utils.ts | 9 +++++++++ 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/react-router/.changes/patch.error-serialization.md b/packages/react-router/.changes/patch.error-serialization.md index 14807fe588..3d70cdd6d6 100644 --- a/packages/react-router/.changes/patch.error-serialization.md +++ b/packages/react-router/.changes/patch.error-serialization.md @@ -1 +1 @@ -Remove the un-documented custom error serialization logic. +Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). diff --git a/packages/react-router/__tests__/vendor/turbo-stream-test.ts b/packages/react-router/__tests__/vendor/turbo-stream-test.ts index f5bfc40884..1bb967dd2a 100644 --- a/packages/react-router/__tests__/vendor/turbo-stream-test.ts +++ b/packages/react-router/__tests__/vendor/turbo-stream-test.ts @@ -185,7 +185,8 @@ test("should encode and decode an Error", async () => { test("should encode and decode an EvalError", async () => { const input = new EvalError("foo"); const output = await quickDecode(encode(input)); - expect(output).toEqual(new Error("foo")); + expect(output).toEqual(input); + expect((output as EvalError).name).toEqual("EvalError"); }); test("should encode and decode array", async () => { diff --git a/packages/react-router/lib/dom/ssr/single-fetch.tsx b/packages/react-router/lib/dom/ssr/single-fetch.tsx index aed8994590..6e02210f4a 100644 --- a/packages/react-router/lib/dom/ssr/single-fetch.tsx +++ b/packages/react-router/lib/dom/ssr/single-fetch.tsx @@ -1,6 +1,9 @@ import * as React from "react"; -import { decode } from "../../../vendor/turbo-stream-v2/turbo-stream"; +import { + SUPPORTED_ERROR_TYPES, + decode, +} from "../../../vendor/turbo-stream-v2/turbo-stream"; import type { Router as DataRouter } from "../../router/router"; import { isDataWithResponseInit, isResponse } from "../../router/router"; import type { @@ -757,8 +760,13 @@ export function decodeViaTurboStream( string | undefined, ]; let Constructor = Error; - // @ts-expect-error - if (name && name in global && typeof global[name] === "function") { + if ( + name && + SUPPORTED_ERROR_TYPES.includes(name) && + name in global && + // @ts-expect-error + typeof global[name] === "function" + ) { // @ts-expect-error Constructor = global[name]; } diff --git a/packages/react-router/vendor/turbo-stream-v2/flatten.ts b/packages/react-router/vendor/turbo-stream-v2/flatten.ts index 14415ccd44..2b75d95d70 100644 --- a/packages/react-router/vendor/turbo-stream-v2/flatten.ts +++ b/packages/react-router/vendor/turbo-stream-v2/flatten.ts @@ -172,6 +172,9 @@ async function stringify(this: ThisEncode, stack: [unknown, number][]) { deferred[index] = input; } else if (input instanceof Error) { str[index] = `["${TYPE_ERROR}",${JSON.stringify(input.message)}`; + if (input.name !== "Error") { + str[index] += `,${JSON.stringify(input.name)}`; + } str[index] += "]"; } else if (Object.getPrototypeOf(input) === null) { str[index] = `["${TYPE_NULL_OBJECT}",{${partsForObj(input)}}]`; diff --git a/packages/react-router/vendor/turbo-stream-v2/turbo-stream.ts b/packages/react-router/vendor/turbo-stream-v2/turbo-stream.ts index e6d7824833..48ff40ef95 100644 --- a/packages/react-router/vendor/turbo-stream-v2/turbo-stream.ts +++ b/packages/react-router/vendor/turbo-stream-v2/turbo-stream.ts @@ -2,6 +2,7 @@ import { flatten } from "./flatten"; import { unflatten } from "./unflatten"; import { Deferred, + SUPPORTED_ERROR_TYPES, TYPE_ERROR, TYPE_PREVIOUS_RESOLVED, TYPE_PROMISE, @@ -13,6 +14,7 @@ import { } from "./utils"; export type { DecodePlugin, EncodePlugin }; +export { SUPPORTED_ERROR_TYPES }; export async function decode( readable: ReadableStream, diff --git a/packages/react-router/vendor/turbo-stream-v2/unflatten.ts b/packages/react-router/vendor/turbo-stream-v2/unflatten.ts index 9c3934a33e..86e45358ad 100644 --- a/packages/react-router/vendor/turbo-stream-v2/unflatten.ts +++ b/packages/react-router/vendor/turbo-stream-v2/unflatten.ts @@ -19,6 +19,7 @@ import { TYPE_SYMBOL, TYPE_URL, type ThisDecode, + SUPPORTED_ERROR_TYPES, } from "./utils"; const globalObj = ( @@ -182,8 +183,15 @@ function hydrate(this: ThisDecode, index: number): any { } continue; case TYPE_ERROR: - const [, message] = value; - let error = new Error(message); + const [, message, errorType] = value; + let error = + errorType && + globalObj && + SUPPORTED_ERROR_TYPES.includes(errorType) && + errorType in globalObj && + typeof globalObj[errorType] === "function" + ? new globalObj[errorType](message) + : new Error(message); hydrated[index] = error; set(error); continue; diff --git a/packages/react-router/vendor/turbo-stream-v2/utils.ts b/packages/react-router/vendor/turbo-stream-v2/utils.ts index e091c55318..51ccc7765f 100644 --- a/packages/react-router/vendor/turbo-stream-v2/utils.ts +++ b/packages/react-router/vendor/turbo-stream-v2/utils.ts @@ -44,6 +44,15 @@ export interface ThisEncode { signal?: AbortSignal; } +export const SUPPORTED_ERROR_TYPES = [ + "EvalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError", +]; + export class Deferred { promise: Promise; resolve!: (value: T) => void; From e102c27ad0caaae4780a07b517045d80e7340a26 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 20 Apr 2026 17:03:50 -0400 Subject: [PATCH 28/36] Migrate changeset -> change file --- .../.changes/unstable.three-buses-develop.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename .changeset/three-buses-develop.md => packages/react-router-dev/.changes/unstable.three-buses-develop.md (80%) diff --git a/.changeset/three-buses-develop.md b/packages/react-router-dev/.changes/unstable.three-buses-develop.md similarity index 80% rename from .changeset/three-buses-develop.md rename to packages/react-router-dev/.changes/unstable.three-buses-develop.md index 1031b967bc..5ab7c41844 100644 --- a/.changeset/three-buses-develop.md +++ b/packages/react-router-dev/.changes/unstable.three-buses-develop.md @@ -1,7 +1,3 @@ ---- -"@react-router/dev": patch ---- - -For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. +For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) - โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. From 0c1a0ff9b62f6b6cc548db32e6a498871d8debcc Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 20 Apr 2026 17:09:03 -0400 Subject: [PATCH 29/36] Update release finish script --- scripts/changes/release.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/changes/release.sh b/scripts/changes/release.sh index f98f183c66..9fc01ad15a 100755 --- a/scripts/changes/release.sh +++ b/scripts/changes/release.sh @@ -76,13 +76,22 @@ elif [[ "${COMMAND}" == "finish" ]]; then git push - git branch -d release git branch -d release-pr &> /dev/null || true + git branch -d release + + set +e git ls-remote --exit-code --heads origin release-pr EXIT_CODE=$? if [[ $EXIT_CODE == '0' ]]; then git push origin --delete release-pr fi + + git ls-remote --exit-code --heads origin release + EXIT_CODE=$? + if [[ $EXIT_CODE == '0' ]]; then + git push origin --delete release + fi + set -e fi set +e From d81965079ba8e5b1f2d08922e2991df80f75db82 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 21 Apr 2026 09:56:34 -0400 Subject: [PATCH 30/36] Update PR preview comment for easier copy/pasting --- scripts/pr-preview.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/pr-preview.ts b/scripts/pr-preview.ts index 7a2b219371..19eab42ec7 100644 --- a/scripts/pr-preview.ts +++ b/scripts/pr-preview.ts @@ -71,16 +71,19 @@ async function comment() { ${STICKY_MARKER} ### Preview Build Available -Preview builds have been created for this PR. You can install them using: +Preview builds have been created for this PR. You can install \`react-router\` using: \`\`\`sh -# Install react-router pnpm install "remix-run/react-router#${branch}&path:packages/react-router" +\`\`\` + +And/or install other packages via: -# Install other packages as necessary +\`\`\`sh +pnpm install "remix-run/react-router#${branch}&path:packages/react-router-dev" +pnpm install "remix-run/react-router#${branch}&path:packages/react-router-express" pnpm install "remix-run/react-router#${branch}&path:packages/react-router-node" pnpm install "remix-run/react-router#${branch}&path:packages/react-router-serve" -pnpm install "remix-run/react-router#${branch}&path:packages/react-router-dev" \`\`\` These preview builds will be updated automatically as you push new commits.`; From cf1d25003aa1217dc21c16e95d483601940ae9af Mon Sep 17 00:00:00 2001 From: Ryan Florence Date: Tue, 21 Apr 2026 08:14:11 -0600 Subject: [PATCH 31/36] Release v7.14.2 (#14993) Co-authored-by: Remix Run Bot --- CHANGELOG.md | 64 +++++++++++++++++++ packages/create-react-router/CHANGELOG.md | 6 ++ packages/create-react-router/package.json | 2 +- packages/react-router-architect/CHANGELOG.md | 8 +++ packages/react-router-architect/package.json | 2 +- packages/react-router-cloudflare/CHANGELOG.md | 7 ++ packages/react-router-cloudflare/package.json | 2 +- ...patch.fix-typegen-layouts-without-pages.md | 4 -- .../.changes/unstable.three-buses-develop.md | 3 - packages/react-router-dev/CHANGELOG.md | 21 ++++++ packages/react-router-dev/package.json | 2 +- packages/react-router-dom/CHANGELOG.md | 7 ++ packages/react-router-dom/package.json | 2 +- packages/react-router-express/CHANGELOG.md | 8 +++ packages/react-router-express/package.json | 2 +- packages/react-router-fs-routes/CHANGELOG.md | 7 ++ packages/react-router-fs-routes/package.json | 2 +- packages/react-router-node/CHANGELOG.md | 7 ++ packages/react-router-node/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/react-router-serve/CHANGELOG.md | 9 +++ packages/react-router-serve/package.json | 2 +- .../.changes/patch.error-serialization.md | 1 - ...tcher-loader-parent-middleware-redirect.md | 1 - .../patch.fix-dom-router-provider-prop.md | 1 - ...roved-types-for-generatepaths-param-arg.md | 35 ---------- packages/react-router/CHANGELOG.md | 46 +++++++++++++ packages/react-router/package.json | 2 +- 29 files changed, 208 insertions(+), 56 deletions(-) delete mode 100644 packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md delete mode 100644 packages/react-router-dev/.changes/unstable.three-buses-develop.md delete mode 100644 packages/react-router/.changes/patch.error-serialization.md delete mode 100644 packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md delete mode 100644 packages/react-router/.changes/patch.fix-dom-router-provider-prop.md delete mode 100644 packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8908b1787b..085388ac58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ We manage release notes in this file instead of the paginated Github Releases Pa Table of Contents - [React Router Releases](#react-router-releases) + - [v7.14.2](#v7142) - [v7.14.1](#v7141) - [v7.14.0](#v7140) - [v7.13.2](#v7132) @@ -168,6 +169,69 @@ We manage release notes in this file instead of the paginated Github Releases Pa +## v7.14.2 + +Date: 2026-04-20 + +### Patch Changes + +- `react-router` - Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + +- `react-router` - Properly handle parent middleware redirects during `fetcher.load` ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + +- `react-router` - Remove redundant `Omit` from `react-router/dom` `RouterProvider` ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + +- `react-router` - Improved types for `generatePath`'s `param` arg ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + + Type errors when required params are omitted: + + ```ts + // Before + // Passes type checks, but throws at runtime ๐Ÿ’ฅ + generatePath(":required", { required: null }); + + // After + generatePath(":required", { required: null }); + // ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) + ``` + + Allow omission of optional params: + + ```ts + // Before + generatePath(":optional?", {}); + // ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) + + // After + generatePath(":optional?", {}); + ``` + + Allows extra keys: + + ```ts + // Before + generatePath(":a", { a: "1", b: "2" }); + // ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) + + // After + generatePath(":a", { a: "1", b: "2" }); + ``` + +- `@react-router/dev` - Fix typegen for layouts without pages ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + + Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages. + Now, `pages: never;` is correctly generated for those cases. + +### Unstable Changes + +โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- `@react-router/dev` - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + + - โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. + +**Full Changelog**: [`v7.14.1...v7.14.2`](https://github.com/remix-run/react-router/compare/react-router@7.14.1...react-router@7.14.2) + ## v7.14.1 Date: 2026-04-13 diff --git a/packages/create-react-router/CHANGELOG.md b/packages/create-react-router/CHANGELOG.md index b108484a89..3a28edf758 100644 --- a/packages/create-react-router/CHANGELOG.md +++ b/packages/create-react-router/CHANGELOG.md @@ -1,5 +1,11 @@ # `create-react-router` +## v7.14.2 + +### Patch Changes + +- _No changes_ + ## v7.14.1 ### Patch Changes diff --git a/packages/create-react-router/package.json b/packages/create-react-router/package.json index 5ba5acde18..c05ba22ba3 100644 --- a/packages/create-react-router/package.json +++ b/packages/create-react-router/package.json @@ -1,6 +1,6 @@ { "name": "create-react-router", - "version": "7.14.1", + "version": "7.14.2", "description": "Create a new React Router app", "homepage": "https://reactrouter.com", "bugs": { diff --git a/packages/react-router-architect/CHANGELOG.md b/packages/react-router-architect/CHANGELOG.md index 38408a24b5..d16b8c1782 100644 --- a/packages/react-router-architect/CHANGELOG.md +++ b/packages/react-router-architect/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/architect` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + - [`@react-router/node@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json index 84b108e664..60aee5f3e3 100644 --- a/packages/react-router-architect/package.json +++ b/packages/react-router-architect/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/architect", - "version": "7.14.1", + "version": "7.14.2", "description": "Architect server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-cloudflare/CHANGELOG.md b/packages/react-router-cloudflare/CHANGELOG.md index cbf276476e..f7cf5ab018 100644 --- a/packages/react-router-cloudflare/CHANGELOG.md +++ b/packages/react-router-cloudflare/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/cloudflare` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-cloudflare/package.json b/packages/react-router-cloudflare/package.json index 2720ca0ec9..6087fc99d0 100644 --- a/packages/react-router-cloudflare/package.json +++ b/packages/react-router-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/cloudflare", - "version": "7.14.1", + "version": "7.14.2", "description": "Cloudflare platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md b/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md deleted file mode 100644 index 138b0900bb..0000000000 --- a/packages/react-router-dev/.changes/patch.fix-typegen-layouts-without-pages.md +++ /dev/null @@ -1,4 +0,0 @@ -Fix typegen for layouts without pages - -Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages. -Now, `pages: never;` is correctly generated for those cases. diff --git a/packages/react-router-dev/.changes/unstable.three-buses-develop.md b/packages/react-router-dev/.changes/unstable.three-buses-develop.md deleted file mode 100644 index 5ab7c41844..0000000000 --- a/packages/react-router-dev/.changes/unstable.three-buses-develop.md +++ /dev/null @@ -1,3 +0,0 @@ -For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) - -- โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. diff --git a/packages/react-router-dev/CHANGELOG.md b/packages/react-router-dev/CHANGELOG.md index fb5b992926..b0fbfbb3d8 100644 --- a/packages/react-router-dev/CHANGELOG.md +++ b/packages/react-router-dev/CHANGELOG.md @@ -1,5 +1,26 @@ # `@react-router/dev` +## v7.14.2 + +### Patch Changes + +- Fix typegen for layouts without pages ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + + Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages. + Now, `pages: never;` is correctly generated for those cases. + +### Unstable Changes + +โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + + - โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + - [`@react-router/node@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.2) + - [`@react-router/serve@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json index 5d0511d79d..834b4930fa 100644 --- a/packages/react-router-dev/package.json +++ b/packages/react-router-dev/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/dev", - "version": "7.14.1", + "version": "7.14.2", "description": "Dev tools and CLI for React Router", "homepage": "https://reactrouter.com", "bugs": { diff --git a/packages/react-router-dom/CHANGELOG.md b/packages/react-router-dom/CHANGELOG.md index 6a4521c14e..2fd41ac56c 100644 --- a/packages/react-router-dom/CHANGELOG.md +++ b/packages/react-router-dom/CHANGELOG.md @@ -1,5 +1,12 @@ # react-router-dom +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-dom/package.json b/packages/react-router-dom/package.json index bded240e0f..a843ed6dca 100644 --- a/packages/react-router-dom/package.json +++ b/packages/react-router-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-router-dom", - "version": "7.14.1", + "version": "7.14.2", "description": "Declarative routing for React web applications", "keywords": [ "react", diff --git a/packages/react-router-express/CHANGELOG.md b/packages/react-router-express/CHANGELOG.md index e39b32d2e5..bf63dc77a5 100644 --- a/packages/react-router-express/CHANGELOG.md +++ b/packages/react-router-express/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/express` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + - [`@react-router/node@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-express/package.json b/packages/react-router-express/package.json index d4c267422d..24175906b3 100644 --- a/packages/react-router-express/package.json +++ b/packages/react-router-express/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/express", - "version": "7.14.1", + "version": "7.14.2", "description": "Express server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-fs-routes/CHANGELOG.md b/packages/react-router-fs-routes/CHANGELOG.md index 46f6107e6e..290f3b1c80 100644 --- a/packages/react-router-fs-routes/CHANGELOG.md +++ b/packages/react-router-fs-routes/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/fs-routes` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`@react-router/dev@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/dev@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-fs-routes/package.json b/packages/react-router-fs-routes/package.json index b5fbd57738..e217b23532 100644 --- a/packages/react-router-fs-routes/package.json +++ b/packages/react-router-fs-routes/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/fs-routes", - "version": "7.14.1", + "version": "7.14.2", "description": "File system routing conventions for React Router, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-node/CHANGELOG.md b/packages/react-router-node/CHANGELOG.md index 26fd243bd2..4c3a010e1e 100644 --- a/packages/react-router-node/CHANGELOG.md +++ b/packages/react-router-node/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/node` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json index e23af560fb..3bd2dc78c6 100644 --- a/packages/react-router-node/package.json +++ b/packages/react-router-node/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/node", - "version": "7.14.1", + "version": "7.14.2", "description": "Node.js platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md index c828c703e7..a5f649f56f 100644 --- a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md +++ b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/remix-config-routes-adapter` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`@react-router/dev@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/dev@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-remix-routes-option-adapter/package.json b/packages/react-router-remix-routes-option-adapter/package.json index 07e1b06969..59338ddeb4 100644 --- a/packages/react-router-remix-routes-option-adapter/package.json +++ b/packages/react-router-remix-routes-option-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/remix-routes-option-adapter", - "version": "7.14.1", + "version": "7.14.2", "description": "Adapter for Remix's \"routes\" config option, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-serve/CHANGELOG.md b/packages/react-router-serve/CHANGELOG.md index f686db719d..dca4637576 100644 --- a/packages/react-router-serve/CHANGELOG.md +++ b/packages/react-router-serve/CHANGELOG.md @@ -1,5 +1,14 @@ # `@react-router/serve` +## v7.14.2 + +### Patch Changes + +- Updated dependencies: + - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) + - [`@react-router/express@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/express@7.14.2) + - [`@react-router/node@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.2) + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router-serve/package.json b/packages/react-router-serve/package.json index bd3a61cc63..047536e71e 100644 --- a/packages/react-router-serve/package.json +++ b/packages/react-router-serve/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/serve", - "version": "7.14.1", + "version": "7.14.2", "description": "Production application server for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router/.changes/patch.error-serialization.md b/packages/react-router/.changes/patch.error-serialization.md deleted file mode 100644 index 3d70cdd6d6..0000000000 --- a/packages/react-router/.changes/patch.error-serialization.md +++ /dev/null @@ -1 +0,0 @@ -Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). diff --git a/packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md b/packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md deleted file mode 100644 index ffc89b2e71..0000000000 --- a/packages/react-router/.changes/patch.fetcher-loader-parent-middleware-redirect.md +++ /dev/null @@ -1 +0,0 @@ -Properly handle parent middleware redirects during `fetcher.load` diff --git a/packages/react-router/.changes/patch.fix-dom-router-provider-prop.md b/packages/react-router/.changes/patch.fix-dom-router-provider-prop.md deleted file mode 100644 index 39a423917b..0000000000 --- a/packages/react-router/.changes/patch.fix-dom-router-provider-prop.md +++ /dev/null @@ -1 +0,0 @@ -Remove redundant `Omit` from `react-router/dom` `RouterProvider` diff --git a/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md b/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md deleted file mode 100644 index bf28169053..0000000000 --- a/packages/react-router/.changes/patch.improved-types-for-generatepaths-param-arg.md +++ /dev/null @@ -1,35 +0,0 @@ -Improved types for `generatePath`'s `param` arg - -Type errors when required params are omitted: - -```ts -// Before -// Passes type checks, but throws at runtime ๐Ÿ’ฅ -generatePath(":required", { required: null }); - -// After -generatePath(":required", { required: null }); -// ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) -``` - -Allow omission of optional params: - -```ts -// Before -generatePath(":optional?", {}); -// ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) - -// After -generatePath(":optional?", {}); -``` - -Allows extra keys: - -```ts -// Before -generatePath(":a", { a: "1", b: "2" }); -// ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) - -// After -generatePath(":a", { a: "1", b: "2" }); -``` diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 9d467b3e78..b22aeb4e9e 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -1,5 +1,51 @@ # `react-router` +## v7.14.2 + +### Patch Changes + +- Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + +- Properly handle parent middleware redirects during `fetcher.load` ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + +- Remove redundant `Omit` from `react-router/dom` `RouterProvider` ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + +- Improved types for `generatePath`'s `param` arg ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + + Type errors when required params are omitted: + + ```ts + // Before + // Passes type checks, but throws at runtime ๐Ÿ’ฅ + generatePath(":required", { required: null }); + + // After + generatePath(":required", { required: null }); + // ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) + ``` + + Allow omission of optional params: + + ```ts + // Before + generatePath(":optional?", {}); + // ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) + + // After + generatePath(":optional?", {}); + ``` + + Allows extra keys: + + ```ts + // Before + generatePath(":a", { a: "1", b: "2" }); + // ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) + + // After + generatePath(":a", { a: "1", b: "2" }); + ``` + ## v7.14.1 ### Patch Changes diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 5d61d61a85..ed9b2fa660 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "react-router", - "version": "7.14.1", + "version": "7.14.2", "description": "Declarative routing for React", "keywords": [ "react", From 8b1a110843682cce0b60f543959e5f01615703ce Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Tue, 21 Apr 2026 14:24:26 +0000 Subject: [PATCH 32/36] chore: format --- CHANGELOG.md | 3 +-- packages/react-router-dev/CHANGELOG.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 085388ac58..e582ec4b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -224,10 +224,9 @@ Date: 2026-04-20 ### Unstable Changes -โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ +โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ - `@react-router/dev` - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) - - โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. **Full Changelog**: [`v7.14.1...v7.14.2`](https://github.com/remix-run/react-router/compare/react-router@7.14.1...react-router@7.14.2) diff --git a/packages/react-router-dev/CHANGELOG.md b/packages/react-router-dev/CHANGELOG.md index b0fbfbb3d8..4830dafa98 100644 --- a/packages/react-router-dev/CHANGELOG.md +++ b/packages/react-router-dev/CHANGELOG.md @@ -11,11 +11,11 @@ ### Unstable Changes -โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ +โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) - - โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. + - Updated dependencies: - [`react-router@7.14.2`](https://github.com/remix-run/react-router/releases/tag/react-router@7.14.2) - [`@react-router/node@7.14.2`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@7.14.2) From fb7a2a61b2a7f2c92d47d9a67719ae7f80c122cc Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 21 Apr 2026 10:29:02 -0400 Subject: [PATCH 33/36] Update release notes --- CHANGELOG.md | 73 ++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e582ec4b88..d467b70771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -171,62 +171,57 @@ We manage release notes in this file instead of the paginated Github Releases Pa ## v7.14.2 -Date: 2026-04-20 +Date: 2026-04-21 ### Patch Changes -- `react-router` - Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) +- `react-router` - Remove the un-documented custom error serialization logic from the internal turbo-stream implementation. React Router only automatically handles serialization of `Error` and it's standard subtypes (`SyntaxError`, `TypeError`, etc.). ([#14992](https://github.com/remix-run/react-router/pull/14992)) +- `react-router` - Properly handle parent middleware redirects during `fetcher.load` ([#14974](https://github.com/remix-run/react-router/pull/14974)) +- `react-router` - Remove redundant `Omit` from `react-router/dom` `RouterProvider` ([#14874](https://github.com/remix-run/react-router/pull/14874)) +- `react-router` - Improved types for `generatePath`'s `param` arg ([#14984](https://github.com/remix-run/react-router/pull/14984)) + - Type errors when required params are omitted: -- `react-router` - Properly handle parent middleware redirects during `fetcher.load` ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) - -- `react-router` - Remove redundant `Omit` from `react-router/dom` `RouterProvider` ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) - -- `react-router` - Improved types for `generatePath`'s `param` arg ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) - - Type errors when required params are omitted: - - ```ts - // Before - // Passes type checks, but throws at runtime ๐Ÿ’ฅ - generatePath(":required", { required: null }); - - // After - generatePath(":required", { required: null }); - // ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) - ``` + ```ts + // Before + // Passes type checks, but throws at runtime ๐Ÿ’ฅ + generatePath(":required", { required: null }); - Allow omission of optional params: + // After + generatePath(":required", { required: null }); + // ^^^^^^^^ Type 'null' is not assignable to type 'string'.ts(2322) + ``` - ```ts - // Before - generatePath(":optional?", {}); - // ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) + - Allow omission of optional params: - // After - generatePath(":optional?", {}); - ``` + ```ts + // Before + generatePath(":optional?", {}); + // ^^ Property 'optional' is missing in type '{}' but required in type '{ optional: string | null | undefined; }'.ts(2741) - Allows extra keys: + // After + generatePath(":optional?", {}); + ``` - ```ts - // Before - generatePath(":a", { a: "1", b: "2" }); - // ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) + - Allows extra keys: - // After - generatePath(":a", { a: "1", b: "2" }); - ``` + ```ts + // Before + generatePath(":a", { a: "1", b: "2" }); + // ^ Object literal may only specify known properties, and 'b' does not exist in type '{ a: string; }'.ts(2353) -- `@react-router/dev` - Fix typegen for layouts without pages ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) + // After + generatePath(":a", { a: "1", b: "2" }); + ``` - Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages. - Now, `pages: never;` is correctly generated for those cases. +- `@react-router/dev` - Fix typegen for layouts without pages ([#14875](https://github.com/remix-run/react-router/pull/14875)) + - Previously, typegen could produce `pages: ;` in `.react-router/types/+routes.ts` when a route corresponded to 0 pages + - Now, `pages: never;` is correctly generated for those cases ### Unstable Changes โš ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ -- `@react-router/dev` - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules. ([#14965](https://github.com/remix-run/react-router/pull/14965)) ([[aabf4a1](https://github.com/remix-run/react-router/commit/aabf4a1)) +- `@react-router/dev` - For `unstable_reactRouterRSC` Vite plugin consumers, require `@vitejs/plugin-react` in user Vite config, and more reliably split route modules ([#14965](https://github.com/remix-run/react-router/pull/14965)) - โš ๏ธ This is a breaking change if you have begun using the `unstable_reactRouterRSC` Vite plugin - please install `@vitejs/plugin-react` and add the `react` plugin to your Vite plugins array. **Full Changelog**: [`v7.14.1...v7.14.2`](https://github.com/remix-run/react-router/compare/react-router@7.14.1...react-router@7.14.2) From b861e71031d8e2bfe49830331fcb171f19cb5798 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 21 Apr 2026 10:42:14 -0400 Subject: [PATCH 34/36] Update GH actions, fix release notes PR annotations --- .github/workflows/release.yml | 9 +++++---- scripts/changes/changes.ts | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76e4153f51..7dcd158e93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: has_change_files: ${{ steps.check.outputs.has_change_files }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Check for change files id: check @@ -59,15 +59,16 @@ jobs: pull-requests: write # enable opening a PR for the release steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: + fetch-depth: 0 token: ${{ secrets.FORMAT_PAT }} - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 24 # Needed to run typescript scripts directly cache: pnpm diff --git a/scripts/changes/changes.ts b/scripts/changes/changes.ts index 0e86206a7b..6e5f1644bc 100644 --- a/scripts/changes/changes.ts +++ b/scripts/changes/changes.ts @@ -402,11 +402,11 @@ function hasBreakingChangePrefix(content: string): boolean { function formatChangelogEntry(change: ChangeFile): string { let lines = change.content.trim().split("\n"); let base = "https://github.com/remix-run/react-router"; - let link = change.prNumber - ? ` ([#${change.prNumber}](${base}/pull/${change.prNumber}))` - : change.gitSha - ? ` ([[${change.gitSha}](${base}/commit/${change.gitSha}))` - : ""; + // prettier-ignore + let link = + change.prNumber ? ` ([#${change.prNumber}](${base}/pull/${change.prNumber}))` : + change.gitSha ? ` ([${change.gitSha}](${base}/commit/${change.gitSha}))` : + ""; if (lines.length === 1) { return `- ${lines[0]}${link}`; From 8d79c829fa40779bf0dbaada3bc61dc47d8da269 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 21 Apr 2026 10:50:28 -0400 Subject: [PATCH 35/36] Move release comments to a standalone workflow for manual dispatch --- .github/workflows/release-comments.yml | 39 ++++++++++++++++++++++++++ .github/workflows/release.yml | 25 +---------------- 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/release-comments.yml diff --git a/.github/workflows/release-comments.yml b/.github/workflows/release-comments.yml new file mode 100644 index 0000000000..147ca2ffe9 --- /dev/null +++ b/.github/workflows/release-comments.yml @@ -0,0 +1,39 @@ +name: ๐Ÿ’ฌ Release Comments + +on: + workflow_call: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + comment: + name: ๐Ÿ“ Comment on released issues/pull requests + runs-on: ubuntu-latest + permissions: + issues: write # enable commenting on released issues + pull-requests: write # enable commenting on released pull requests + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + + - name: โŽ” Setup node + uses: actions/setup-node@v6 + with: + node-version: 24 # Needed for node TS support + cache: "pnpm" + + - name: ๐Ÿ“ฅ Install deps + run: pnpm install --frozen-lockfile + + - name: ๐Ÿ“ Comment on released issues and pull requests + env: + GH_TOKEN: ${{ github.token }} + run: pnpm run release-comments diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7dcd158e93..e2fa9bdfb2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,30 +116,7 @@ jobs: comment: name: ๐Ÿ“ Comment on released issues/pull requests needs: publish - runs-on: ubuntu-latest - permissions: - issues: write # enable commenting on released issues - pull-requests: write # enable commenting on released pull requests - steps: - - name: โฌ‡๏ธ Checkout repo - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: ๐Ÿ“ฆ Setup pnpm - uses: pnpm/action-setup@v4 - - - name: โŽ” Setup node - uses: actions/setup-node@v6 - with: - node-version: 24 # Needed for node TS support - cache: "pnpm" - - - name: ๐Ÿ“ฅ Install deps - run: pnpm install --frozen-lockfile - - - name: ๐Ÿ“ Comment on released issues and pull requests - run: pnpm run release-comments + uses: ./.github/workflows/release-comments.yml experimental-release: name: ๐Ÿงช Experimental Release From 415847ee34dc37fa708b6f22d9ec7923cd3bd077 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 21 Apr 2026 11:19:57 -0400 Subject: [PATCH 36/36] Fix release comment command escaping --- scripts/release-comments.ts | 65 ++++++++++++++++++------------------- scripts/utils/process.ts | 22 ++++++++++++- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/scripts/release-comments.ts b/scripts/release-comments.ts index 9245011f5e..c28aef00ac 100644 --- a/scripts/release-comments.ts +++ b/scripts/release-comments.ts @@ -47,7 +47,7 @@ function findBoundingTags() { `-l ${PACKAGE_NAME}@*`, "--sort -creatordate", "--format %\\(refname:strip=2\\)", - ].join(" "), + ], true, ); let stableGitTags = stdout @@ -79,7 +79,7 @@ function getCommits(from: Tag, to: Tag): Array { "--pretty=format:%H", `${from.raw}...${to.raw}`, DIRECTORY_TO_CHECK!, - ].join(" "), + ], true, ); @@ -99,21 +99,17 @@ async function commentOnPrAndLinkedIssues(pr: MergedPR, latest: Tag) { debug(`[dry-run] would comment on PR #${pr.number}`); } else { // Comment on PR - logAndExec( - ["gh", "pr", "comment", String(pr.number), "--body", prComment].join(" "), - ); + logAndExec(["gh", "pr", "comment", String(pr.number), "--body", prComment]); // Remove PR labels - logAndExec( - [ - "gh", - "pr", - "edit", - String(pr.number), - "--remove-label", - PR_LABELS_TO_REMOVE, - ].join(" "), - ); + logAndExec([ + "gh", + "pr", + "edit", + String(pr.number), + "--remove-label", + PR_LABELS_TO_REMOVE, + ]); } let promises = pr.issues.map((issue) => commentOnIssue(issue, latest)); @@ -153,15 +149,18 @@ async function commentOnIssue(issue: number, latest: Tag) { ); } else { // Comment on linked issue - logAndExec( - ["gh", "issue", "comment", String(issue), "--body", issueComment].join( - " ", - ), - ); + logAndExec([ + "gh", + "issue", + "comment", + String(issue), + "--body", + issueComment, + ]); // Close linked issue if (shouldClose) { - logAndExec(["gh", "issue", "close", String(issue)].join(" ")); + logAndExec(["gh", "issue", "close", String(issue)]); } else { debug( `Skipping close of issue #${issue} due to "${ISSUE_LABELS_TO_KEEP_OPEN}" label`, @@ -169,16 +168,14 @@ async function commentOnIssue(issue: number, latest: Tag) { } // Remove labels from linked issue - logAndExec( - [ - "gh", - "issue", - "edit", - String(issue), - "--remove-label", - ISSUE_LABELS_TO_REMOVE, - ].join(" "), - ); + logAndExec([ + "gh", + "issue", + "edit", + String(issue), + "--remove-label", + ISSUE_LABELS_TO_REMOVE, + ]); } } @@ -233,7 +230,7 @@ async function findMergedPRs( "merged", "--json", "number,title,url,body", - ].join(" "), + ], true, ); @@ -302,7 +299,7 @@ function getIssuesLinkedToPullRequest(prHtmlUrl: string): Array { "--paginate", `--field prHtmlUrl=${prHtmlUrl}`, `--raw-field query='${trimNewlines(query)}'`, - ].join(" "), + ], true, ); @@ -349,7 +346,7 @@ function getIssueLabels(number: string): Array { `--field repo=${repo}`, `--field number=${number}`, `--raw-field query='${trimNewlines(query)}'`, - ].join(" "), + ], true, ); diff --git a/scripts/utils/process.ts b/scripts/utils/process.ts index ef5b6a121d..0fe4ae7f0e 100644 --- a/scripts/utils/process.ts +++ b/scripts/utils/process.ts @@ -15,7 +15,27 @@ export function getRootDir(): string { return process.cwd(); } -export function logAndExec(command: string, captureOutput = false): string { +export function logAndExec(args: string[], captureOutput?: boolean): string; +export function logAndExec(command: string, captureOutput?: boolean): string; +export function logAndExec( + commandOrArgs: string | string[], + captureOutput = false, +): string { + let command: string; + if (typeof commandOrArgs === "string") { + command = commandOrArgs; + } else { + command = [ + commandOrArgs[0], + // Quote each argument + ...commandOrArgs + .slice(1) + .map((arg) => + arg.startsWith("-") ? arg : `'${arg.replaceAll("'", "'\\''")}'`, + ), + ].join(" "); + } + console.log(`$ ${command}`); if (captureOutput) { return cp.execSync(command, { stdio: "pipe", encoding: "utf-8" }).trim();