From 75c6a95bbc74502fadee9952f7fafa7a96be3f69 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Mar 2026 16:48:31 -0800 Subject: [PATCH 1/2] Create prepare-release script and bot pipeline --- .ado/jobs/setup.yml | 10 +- .ado/prepare-release-bot.yml | 79 ++++ .ado/publish.yml | 97 +---- .ado/release.yml | 74 +++- package.json | 1 + .../@rnw-scripts/prepare-release/.eslintrc.js | 4 + .../@rnw-scripts/prepare-release/.gitignore | 2 + packages/@rnw-scripts/prepare-release/bin.js | 11 + .../@rnw-scripts/prepare-release/package.json | 46 ++ .../prepare-release/src/beachballBump.ts | 49 +++ .../@rnw-scripts/prepare-release/src/git.ts | 128 ++++++ .../prepare-release/src/github.ts | 139 ++++++ .../prepare-release/src/prepareRelease.ts | 397 ++++++++++++++++++ .../@rnw-scripts/prepare-release/src/proc.ts | 120 ++++++ .../prepare-release/src/releaseSummary.ts | 139 ++++++ .../prepare-release/tsconfig.json | 5 + 16 files changed, 1194 insertions(+), 107 deletions(-) create mode 100644 .ado/prepare-release-bot.yml create mode 100644 packages/@rnw-scripts/prepare-release/.eslintrc.js create mode 100644 packages/@rnw-scripts/prepare-release/.gitignore create mode 100644 packages/@rnw-scripts/prepare-release/bin.js create mode 100644 packages/@rnw-scripts/prepare-release/package.json create mode 100644 packages/@rnw-scripts/prepare-release/src/beachballBump.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/git.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/github.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/prepareRelease.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/proc.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/releaseSummary.ts create mode 100644 packages/@rnw-scripts/prepare-release/tsconfig.json diff --git a/.ado/jobs/setup.yml b/.ado/jobs/setup.yml index adcfab566c3..ccd5db1aada 100644 --- a/.ado/jobs/setup.yml +++ b/.ado/jobs/setup.yml @@ -34,16 +34,24 @@ jobs: - script: npx lage build --scope @rnw-scripts/beachball-config --no-deps displayName: Build @rnw-scripts/beachball-config - + + - script: | + echo "System.PullRequest.SourceBranch = $(System.PullRequest.SourceBranch)" + echo "Build.SourceBranch = $(Build.SourceBranch)" + echo "Build.SourceBranchName = $(Build.SourceBranchName)" + displayName: Print branch variables + - pwsh: | npx beachball check --branch origin/$(BeachBallBranchName) --verbose 2>&1 | Tee-Object -Variable beachballOutput $beachballErrors = $beachballOutput | Where-Object { $_ -match "ERROR: *"} $beachballErrors | ForEach { Write-Host "##vso[task.logissue type=warning]POSSIBLE $_" } displayName: Warn for possible invalid change files + condition: not(startsWith(variables['System.PullRequest.SourceBranch'], 'prepare-release/')) - ${{ if endsWith(parameters.buildEnvironment, 'PullRequest') }}: - script: npx beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run \"yarn change\" from root of repo to generate a change file." displayName: Check for change files + condition: not(startsWith(variables['System.PullRequest.SourceBranch'], 'prepare-release/')) - script: npx beachball bump --branch origin/$(BeachBallBranchName) --yes --verbose displayName: beachball bump diff --git a/.ado/prepare-release-bot.yml b/.ado/prepare-release-bot.yml new file mode 100644 index 00000000000..89bd28de7cb --- /dev/null +++ b/.ado/prepare-release-bot.yml @@ -0,0 +1,79 @@ +name: $(Date:yyyyMMdd).$(Rev:r) + +# Triggers are configured in the ADO pipeline UI: +# - CI triggers on pushes to main and *-stable branches +# - Scheduled triggers for daily runs +# - Manual runs with optional branch override +trigger: none +pr: none + +parameters: + - name: targetBranch + displayName: Target branch for version bump (use default to use pipeline source branch) + type: string + default: (source branch) + values: + - (source branch) + - main + - 0.82-stable + - 0.81-stable + - 0.80-stable + - 0.74-stable + +jobs: + - job: PrepareRelease + displayName: Prepare Release Bot + pool: + vmImage: windows-latest + timeoutInMinutes: 30 + + steps: + - checkout: self + persistCredentials: true + fetchDepth: 1 + fetchTags: false + + - script: | + git config user.name "React-Native-Windows Bot" + git config user.email "53619745+rnbot@users.noreply.github.com" + displayName: Configure Git Identity + + # Extract OAuth token from persistCredentials for GitHub API access (gh CLI) + - pwsh: | + $headerLine = git config --get-regexp "http.*\.extraheader" 2>$null | Select-Object -First 1 + if (-not $headerLine) { + Write-Host "##[error]No HTTP extraheader found. persistCredentials may not be working." + exit 1 + } + $encoded = ($headerLine.Split(' ')[-1]).Trim() + $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded)) + $token = $decoded.Split(':')[-1] + Write-Host "Extracted GitHub OAuth token (length=$($token.Length))" + Write-Host "##vso[task.setvariable variable=GitHubOAuthToken;issecret=true]$token" + displayName: Extract GitHub OAuth token + + - task: NodeTool@0 + displayName: Set Node Version + inputs: + versionSpec: '24.x' + + - script: if not exist %APPDATA%\npm (mkdir %APPDATA%\npm) + displayName: Ensure npm directory for npx commands + + - script: npx --yes midgard-yarn@1.23.34 --ignore-scripts --frozen-lockfile + displayName: yarn install + + - script: npx lage build --scope @rnw-scripts/prepare-release --scope @rnw-scripts/beachball-config + displayName: Build prepare-release and dependencies + + - ${{ if ne(parameters.targetBranch, '(source branch)') }}: + - pwsh: Write-Host "##vso[task.setvariable variable=TargetBranch]${{ parameters.targetBranch }}" + displayName: Set target branch from parameter + - ${{ else }}: + - pwsh: Write-Host "##vso[task.setvariable variable=TargetBranch]$(Build.SourceBranchName)" + displayName: Set target branch from source + + - script: npx prepare-release --branch $(TargetBranch) --no-color + displayName: Prepare Release + env: + GH_TOKEN: $(GitHubOAuthToken) diff --git a/.ado/publish.yml b/.ado/publish.yml index 66c130aced7..a4370a54020 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -1,18 +1,6 @@ name: 0.0.$(Date:yyMM.d)$(Rev:rrr) parameters: -- name: skipNpmPublish - displayName: Skip Npm Publish - type: boolean - default: false -- name: skipGitPush - displayName: Skip Git Push - type: boolean - default: false -- name: stopOnNoCI - displayName: Stop if latest commit is ***NO_CI*** - type: boolean - default: true - name: performBeachballCheck displayName: Perform Beachball Check (Disable when promoting) type: boolean @@ -104,14 +92,10 @@ parameters: variables: - template: variables/windows.yml - group: RNW Secrets - - name: SkipGitPushPublishArgs - value: '' - name: FailCGOnAlert value: false - name: EnableCodesign value: false - - name: SourceBranchWithFolders - value: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] trigger: none pr: none @@ -142,20 +126,9 @@ extends: timeoutInMinutes: 120 cancelTimeoutInMinutes: 5 steps: - - powershell: | - Write-Host "Stopping because commit message contains ***NO_CI***." - $uri = "https://dev.azure.com/microsoft/ReactNative/_apis/build/builds/$(Build.BuildId)?api-version=5.1" - $json = @{status="Cancelling"} | ConvertTo-Json -Compress - $build = Invoke-RestMethod -Uri $uri -Method Patch -Headers @{Authorization = "Bearer $(System.AccessToken)"} -ContentType "application/json" -Body $json - Write-Host $build - Write-Host "Waiting 60 seconds for build cancellation..." - Start-Sleep -Seconds 60 - displayName: Stop pipeline if latest commit message contains ***NO_CI*** - condition: and(${{ parameters.stopOnNoCI }}, contains(variables['Build.SourceVersionMessage'], '***NO_CI***')) - - template: .ado/templates/checkout-full.yml@self parameters: - persistCredentials: false # We're going to use rnbot's git creds to publish + persistCredentials: false - powershell: gci env:/BUILD_* displayName: Show build information @@ -176,7 +149,7 @@ extends: condition: ${{ parameters.performBeachballCheck }} - job: RnwNpmPublish - displayName: React-Native-Windows Npm Build Rev Publish + displayName: React-Native-Windows Npm Pack dependsOn: RnwPublishPrep pool: name: Azure-Pipelines-1ESPT-ExDShared @@ -189,36 +162,12 @@ extends: parameters: agentImage: HostedImage - - template: .ado/templates/configure-git.yml@self - - - pwsh: | - Write-Host "##vso[task.setvariable variable=SkipGitPushPublishArgs]--no-push" - displayName: Enable No-Publish (git) - condition: ${{ parameters.skipGitPush }} - - # Beachball publishes NPM packages to the "$(Pipeline.Workspace)\published-packages" folder. - # It pushes NPM version updates to Git depending on the SkipGitPushPublishArgs variable derived from the skipGitPush parameter. - - script: | - if exist "$(Pipeline.Workspace)\published-packages" rd /s /q "$(Pipeline.Workspace)\published-packages" - mkdir "$(Pipeline.Workspace)\published-packages" - npx beachball publish --no-publish $(SkipGitPushPublishArgs) --pack-to-path "$(Pipeline.Workspace)\published-packages" --branch origin/$(SourceBranchWithFolders) -yes --bump-deps --verbose --access public --message "applying package updates ***NO_CI***" - displayName: Beachball Publish + - script: node .ado/scripts/npmPack.js --clean --no-color "$(Pipeline.Workspace)\published-packages" + displayName: Pack npm packages - script: dir /s "$(Pipeline.Workspace)\published-packages" displayName: Show created npm packages - # Beachball reverts to local state after publish, but we want the updates it added - - script: git pull origin $(SourceBranchWithFolders) - displayName: git pull - - - script: npx @rnw-scripts/create-github-releases --yes --authToken $(githubAuthToken) - displayName: Create GitHub Releases (New Canary Version) - condition: and(succeeded(), ${{ not(parameters.skipGitPush) }}, ${{ eq(variables['Build.SourceBranchName'], 'main') }} ) - - - script: npx --yes @rnw-scripts/create-github-releases@latest --yes --authToken $(githubAuthToken) - displayName: Create GitHub Releases (New Stable Version) - condition: and(succeeded(), ${{ not(parameters.skipGitPush) }}, ${{ ne(variables['Build.SourceBranchName'], 'main') }} ) - - template: .ado/templates/set-version-vars.yml@self parameters: buildEnvironment: Continuous @@ -226,26 +175,6 @@ extends: - script: echo NpmDistTag is $(NpmDistTag) displayName: Show NPM dist tag - - script: dir /s "$(Pipeline.Workspace)\published-packages" - displayName: Show npm packages before ESRP release - - - task: 'SFP.release-tasks.custom-build-release-task.EsrpRelease@10' - displayName: 'ESRP Release to npmjs.com' - condition: and(succeeded(), ne(variables['NpmDistTag'], '')) - inputs: - connectedservicename: 'ESRP-CodeSigning-OGX-JSHost-RNW' - usemanagedidentity: false - keyvaultname: 'OGX-JSHost-KV' - authcertname: 'OGX-JSHost-Auth4' - signcertname: 'OGX-JSHost-Sign3' - clientid: '0a35e01f-eadf-420a-a2bf-def002ba898d' - domaintenantid: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - contenttype: npm - folderlocation: '$(Pipeline.Workspace)\published-packages' - productstate: '$(NpmDistTag)' - owners: 'vmorozov@microsoft.com' - approvers: 'khosany@microsoft.com' - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: 📒 Generate Manifest Npm inputs: @@ -377,17 +306,6 @@ extends: - template: .ado/templates/component-governance.yml@self - # Make symbols available through http://symweb. - - task: PublishSymbols@2 - displayName: Publish symbols - enabled: false - env: - ARTIFACTSERVICES_SYMBOL_ACCOUNTNAME: microsoft - inputs: - SearchPattern: vnext/target/**/*.pdb - SymbolServerType: TeamServices - Pat: $(System.AccessToken) - templateContext: sdl: binskim: @@ -481,11 +399,11 @@ extends: # Symbol Publishing for Work Item 59264834 - MSRC Compliance - task: PublishSymbols@2 displayName: 'Publish Symbols to Microsoft Symbol Server' - enabled: false + enabled: true condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI')) - env: - ARTIFACTSERVICES_SYMBOL_ACCOUNTNAME: microsoft inputs: + UseNetCoreClientTool: true + ConnectedServiceName: Office-React-Native-Windows-Bot SymbolsFolder: '$(System.DefaultWorkingDirectory)\NugetRoot' SearchPattern: '**/*.pdb' SymbolServerType: 'TeamServices' @@ -494,7 +412,6 @@ extends: SymbolsArtifactName: 'ReactNativeWindows-Symbols' DetailedLog: true TreatNotIndexedAsWarning: false - Pat: $(System.AccessToken) templateContext: sdl: diff --git a/.ado/release.yml b/.ado/release.yml index 146acb2c428..98b0dd104ab 100644 --- a/.ado/release.yml +++ b/.ado/release.yml @@ -82,43 +82,85 @@ extends: - job: PushPrivateAdo displayName: ADO - nuget - react-native - timeoutInMinutes: 0 + templateContext: inputs: - input: pipelineArtifact pipeline: 'Publish' artifactName: 'ReactWindows-final-nuget' targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + steps: - checkout: none - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' - - template: .ado/templates/authenticate-office-react-native-windows-bot.yml@self - - task: CmdLine@2 - displayName: NuGet push (react-native) + + - script: dir /S $(Pipeline.Workspace)\ReactWindows-final-nuget + displayName: Show directory contents + + - task: AzureCLI@2 + displayName: Override NuGet credentials with Managed Identity inputs: - script: nuget.exe push *.nupkg -ApiKey $(oficeReactnativeWindowsBotAadAuthToken) -Source https://pkgs.dev.azure.com/ms/_packaging/react-native/nuget/v3/index.json -NonInteractive -Verbosity Detailed -SkipDuplicate -NoSymbols - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + azureSubscription: 'Office-React-Native-Windows-Bot' + visibleAzLogin: false + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + $accessToken = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv + # Set the access token as a secret, so it doesn't get leaked in the logs + Write-Host "##vso[task.setsecret]$accessToken" + # Override the apitoken of the nuget service connection, for the duration of this stage + Write-Host "##vso[task.setendpoint id=a7e33797-4804-4a1d-911d-5bd325e50a85;field=authParameter;key=apitoken]$accessToken" + + - task: 1ES.PublishNuGet@1 + displayName: NuGet push to ms/react-native-public + inputs: + useDotNetTask: true + packageParentPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + packagesToPush: '$(Pipeline.Workspace)/ReactWindows-final-nuget/*.nupkg' + nuGetFeedType: external + publishFeedCredentials: 'ms/react-native ADO Feed' + externalEndpoint: 'ms/react-native ADO Feed' + publishPackageMetadata: true - job: PushPublicAdo displayName: ADO - nuget - react-native-public - timeoutInMinutes: 0 + templateContext: inputs: - input: pipelineArtifact pipeline: 'Publish' artifactName: 'ReactWindows-final-nuget' targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + steps: - checkout: none - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' - - template: .ado/templates/authenticate-office-react-native-windows-bot.yml@self - - task: CmdLine@2 - displayName: NuGet push (react-native-public) + + - script: dir /S $(Pipeline.Workspace)\ReactWindows-final-nuget + displayName: Show directory contents + + - task: AzureCLI@2 + displayName: Override NuGet credentials with Managed Identity inputs: - script: nuget.exe push *.nupkg -ApiKey $(oficeReactnativeWindowsBotAadAuthToken) -Source https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json -NonInteractive -Verbosity Detailed -SkipDuplicate -NoSymbols - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + azureSubscription: 'Office-React-Native-Windows-Bot' + visibleAzLogin: false + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + $accessToken = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv + # Set the access token as a secret, so it doesn't get leaked in the logs + Write-Host "##vso[task.setsecret]$accessToken" + # Override the apitoken of the nuget service connection, for the duration of this stage + Write-Host "##vso[task.setendpoint id=9a2456d0-c163-405b-be24-c03fd74b155a;field=authParameter;key=apitoken]$accessToken" + + - task: 1ES.PublishNuGet@1 + displayName: NuGet push to ms/react-native-public + inputs: + useDotNetTask: true + packageParentPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + packagesToPush: '$(Pipeline.Workspace)/ReactWindows-final-nuget/*.nupkg' + nuGetFeedType: external + publishFeedCredentials: 'ms/react-native-public ADO Feed' + externalEndpoint: 'ms/react-native-public ADO Feed' + publishPackageMetadata: true - job: PushNuGetOrg displayName: nuget.org - Push nuget packages diff --git a/package.json b/package.json index 50c2ad59d3d..cbf88c4b618 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@rnw-scripts/format-files": "1.1.59", "@rnw-scripts/integrate-rn": "1.4.65", "@rnw-scripts/just-task": "2.3.56", + "@rnw-scripts/prepare-release": "*", "@rnw-scripts/promote-release": "2.1.63", "@rnw-scripts/stamp-version": "0.0.0", "@rnw-scripts/take-screenshot": "1.1.63", diff --git a/packages/@rnw-scripts/prepare-release/.eslintrc.js b/packages/@rnw-scripts/prepare-release/.eslintrc.js new file mode 100644 index 00000000000..35e0d115126 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['@rnw-scripts'], + parserOptions: {tsconfigRootDir : __dirname}, +}; diff --git a/packages/@rnw-scripts/prepare-release/.gitignore b/packages/@rnw-scripts/prepare-release/.gitignore new file mode 100644 index 00000000000..f42efbb9f7c --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/.gitignore @@ -0,0 +1,2 @@ +lib/ +lib-commonjs/ diff --git a/packages/@rnw-scripts/prepare-release/bin.js b/packages/@rnw-scripts/prepare-release/bin.js new file mode 100644 index 00000000000..aae8dc76ae9 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/bin.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * @format + */ + +require('source-map-support').install(); +require('./lib-commonjs/prepareRelease'); diff --git a/packages/@rnw-scripts/prepare-release/package.json b/packages/@rnw-scripts/prepare-release/package.json new file mode 100644 index 00000000000..065835f4adb --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/package.json @@ -0,0 +1,46 @@ +{ + "name": "@rnw-scripts/prepare-release", + "version": "0.0.1", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/react-native-windows", + "directory": "packages/@rnw-scripts/prepare-release" + }, + "scripts": { + "build": "rnw-scripts build", + "clean": "rnw-scripts clean", + "lint": "rnw-scripts lint", + "lint:fix": "rnw-scripts lint:fix", + "watch": "rnw-scripts watch" + }, + "main": "lib-commonjs/prepareRelease.js", + "bin": { + "prepare-release": "./bin.js" + }, + "dependencies": { + "@react-native-windows/find-repo-root": "^0.0.0-canary.99", + "@react-native-windows/fs": "^0.0.0-canary.70", + "@react-native-windows/package-utils": "^0.0.0-canary.96", + "source-map-support": "^0.5.19" + }, + "devDependencies": { + "@rnw-scripts/eslint-config": "1.2.38", + "@rnw-scripts/just-task": "2.3.58", + "@rnw-scripts/ts-config": "2.0.6", + "@types/node": "^22.14.0", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "eslint": "^8.19.0", + "prettier": "2.8.8", + "typescript": "5.0.4" + }, + "files": [ + "bin.js", + "lib-commonjs" + ], + "engines": { + "node": ">= 22" + } +} diff --git a/packages/@rnw-scripts/prepare-release/src/beachballBump.ts b/packages/@rnw-scripts/prepare-release/src/beachballBump.ts new file mode 100644 index 00000000000..8658d308d5e --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/beachballBump.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Beachball change file detection and version bump invocation. + * + * @format + */ + +import fs from '@react-native-windows/fs'; +import path from 'path'; + +import {exec} from './proc'; + +/** + * Check whether there are pending beachball change files in the repo. + * + * Beachball stores change files as JSON in the `change/` directory at the + * repo root. If there are any .json files there, there are pending changes. + */ +export function hasChangeFiles(repoRoot: string): boolean { + const changeDir = path.join(repoRoot, 'change'); + if (!fs.existsSync(changeDir)) { + return false; + } + + const entries = fs.readdirSync(changeDir); + return entries.some(entry => entry.endsWith('.json')); +} + +/** + * Run beachball bump to consume change files and update versions/changelogs. + * + * Invokes: npx beachball bump --branch / --yes --verbose + * + * The --yes flag suppresses prompts. + * The --verbose flag provides detailed output. + * The --branch flag tells beachball the baseline branch to diff against. + */ +export async function bumpVersions(opts: { + targetBranch: string; + remote: string; + cwd: string; +}): Promise { + await exec( + `npx beachball bump --branch ${opts.remote}/${opts.targetBranch} --yes --verbose`, + {cwd: opts.cwd}, + ); +} diff --git a/packages/@rnw-scripts/prepare-release/src/git.ts b/packages/@rnw-scripts/prepare-release/src/git.ts new file mode 100644 index 00000000000..8afe4cc9b44 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/git.ts @@ -0,0 +1,128 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Git operations module. Provides a typed wrapper around git CLI commands. + * Simplified from fork-sync/src/modules/git.ts. + * + * @format + */ + +import {spawn} from './proc'; + +/** + * Typed wrapper around a git repository directory. + * All methods execute git commands with cwd set to the wrapped directory. + */ +export class GitRepo { + readonly dir: string; + + constructor(dir: string) { + this.dir = dir; + } + + /** Fetch from a remote. */ + async fetch(remote: string): Promise { + await this.git('fetch', remote); + } + + /** Checkout an existing ref (branch, tag, or commit). */ + async checkout(ref: string): Promise { + await this.git('checkout', ref); + } + + /** + * Create or reset a branch to a start point. + * Uses `git checkout -B` which creates the branch if it doesn't exist, + * or resets it if it does. + */ + async checkoutNewBranch(name: string, startPoint?: string): Promise { + const args = ['checkout', '-B', name]; + if (startPoint) { + args.push(startPoint); + } + await this.git(...args); + } + + /** Stage all changes (git add --all). */ + async stageAll(): Promise { + await this.git('add', '--all'); + } + + /** Create a commit with the given message. */ + async commit(message: string): Promise { + await this.git('commit', '-m', message); + } + + /** Push a branch to a remote, optionally with --force. */ + async push( + remote: string, + branch: string, + opts?: {force?: boolean}, + ): Promise { + const args = ['push', remote, branch]; + if (opts?.force) { + args.push('--force'); + } + await this.git(...args); + } + + /** Resolve a ref to its SHA (git rev-parse). */ + async revParse(ref: string): Promise { + return this.git('rev-parse', ref); + } + + /** Get the current branch name, or 'HEAD' if in detached state. */ + async currentBranch(): Promise { + return this.git('rev-parse', '--abbrev-ref', 'HEAD'); + } + + /** Check for uncommitted changes (git status --porcelain). */ + async statusPorcelain(pathspec?: string): Promise { + const args = ['status', '--porcelain']; + if (pathspec) { + args.push('--', pathspec); + } + return this.git(...args); + } + + /** List all remotes with their URLs (git remote -v). */ + async remoteList(): Promise { + return this.git('remote', '-v'); + } + + /** Git log with format and optional range. */ + async log(opts: {format: string; range?: string}): Promise { + const args = ['log', `--format=${opts.format}`]; + if (opts.range) { + args.push(opts.range); + } + return this.git(...args); + } + + /** Get file names changed between two refs, optionally filtered by path. */ + async diffNameOnly( + ref1: string, + ref2?: string, + pathspec?: string, + ): Promise { + const args = ['diff', '--name-only', ref1]; + if (ref2) { + args.push(ref2); + } + if (pathspec) { + args.push('--', pathspec); + } + return this.git(...args); + } + + /** Check if a branch exists on a remote. Returns the ls-remote output or empty. */ + async lsRemote(remote: string, branch: string): Promise { + return this.git('ls-remote', '--heads', remote, `refs/heads/${branch}`); + } + + /** Internal helper: run git with the repo's cwd. */ + private async git(...args: string[]): Promise { + return spawn('git', args, {cwd: this.dir}); + } +} diff --git a/packages/@rnw-scripts/prepare-release/src/github.ts b/packages/@rnw-scripts/prepare-release/src/github.ts new file mode 100644 index 00000000000..07ad6e264ae --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/github.ts @@ -0,0 +1,139 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * GitHub operations via the gh CLI. + * + * @format + */ + +import fs from '@react-native-windows/fs'; +import os from 'os'; +import path from 'path'; + +import {exec} from './proc'; + +export interface PRInfo { + number: number; + url: string; +} + +/** + * Run a callback with a temporary file containing the given content. + * The file is cleaned up after the callback completes (success or failure). + */ +async function withTempFile( + content: string, + fn: (filePath: string) => Promise, +): Promise { + const tmpFile = path.join( + os.tmpdir(), + `gh-pr-body-${process.pid}-${Date.now()}.md`, + ); + fs.writeFileSync(tmpFile, content, 'utf8'); + try { + return await fn(tmpFile); + } finally { + try { + fs.unlinkSync(tmpFile); + } catch { + // Ignore cleanup errors + } + } +} + +/** + * Find an existing open PR by head branch name. + * Returns null if no matching PR exists. + */ +export async function findPR(opts: { + head: string; + cwd: string; + repo?: string; +}): Promise { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + const result = await exec( + `gh pr list --head "${opts.head}" --json number,url --limit 1${repoFlag}`, + {cwd: opts.cwd, fallback: '[]'}, + ); + + let prs: Array<{number: number; url: string}>; + try { + prs = JSON.parse(result); + } catch { + return null; + } + + if (!Array.isArray(prs) || prs.length === 0) { + return null; + } + + return {number: prs[0].number, url: prs[0].url}; +} + +/** + * Create a new pull request. Returns the PR number and URL. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +export async function createPR(opts: { + head: string; + base: string; + title: string; + body: string; + cwd: string; + repo?: string; +}): Promise { + return withTempFile(opts.body, async bodyFile => { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + const result = await exec( + `gh pr create --head "${opts.head}" --base "${opts.base}"` + + ` --title "${escapeForShell(opts.title)}"` + + ` --body-file "${bodyFile}"${repoFlag}`, + {cwd: opts.cwd}, + ); + + // gh pr create outputs the PR URL on success + const url = result.trim(); + const match = url.match(/\/pull\/(\d+)/); + const number = match ? parseInt(match[1], 10) : 0; + + return {number, url}; + }); +} + +/** + * Update an existing pull request's body text. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +export async function updatePR(opts: { + number: number; + title: string; + body: string; + cwd: string; + repo?: string; +}): Promise { + await withTempFile(opts.body, async bodyFile => { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + await exec( + `gh pr edit ${opts.number} --title "${escapeForShell(opts.title)}"` + + ` --body-file "${bodyFile}"${repoFlag}`, + {cwd: opts.cwd}, + ); + }); +} + +/** + * Escape a string for safe inclusion in a double-quoted shell argument. + * Used for single-line values like titles; multiline content uses --body-file. + */ +function escapeForShell(str: string): string { + return str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\$/g, '\\$') + .replace(/`/g, '\\`'); +} diff --git a/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts b/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts new file mode 100644 index 00000000000..0b247386978 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts @@ -0,0 +1,397 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * This script automates the version bump PR workflow. It checks for pending + * beachball change files, bumps versions, and creates or updates a + * "Version Packages" pull request. + * + * Usage: + * npx prepare-release --branch [--dry-run] [--no-color] [--help] + * + * @format + */ + +import {parseArgs} from 'node:util'; +import fs from '@react-native-windows/fs'; +import path from 'path'; + +import findRepoRoot from '@react-native-windows/find-repo-root'; + +import {GitRepo} from './git'; +import {findPR, createPR, updatePR} from './github'; +import {hasChangeFiles, bumpVersions} from './beachballBump'; +import { + collectBumpedPackages, + generatePRBody, + generateConsoleSummary, +} from './releaseSummary'; + +// --------------------------------------------------------------------------- +// Color utilities (from npmPack.js pattern) +// --------------------------------------------------------------------------- + +const ansi = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + gray: '\x1b[90m', +}; + +let useColors = true; + +function colorize(text: string, color: string): string { + if (!useColors) { + return text; + } + return color + text + ansi.reset; +} + +// --------------------------------------------------------------------------- +// CLI help +// --------------------------------------------------------------------------- + +function showHelp(): void { + console.log(` +prepare-release - Automate version bump PRs using beachball + +Usage: + npx prepare-release --branch [options] + +Options: + --branch Target branch to prepare release for (required) + --dry-run Do everything except push and PR create/update + --no-color Disable colored output + --help, -h Show this help message + +Examples: + npx prepare-release --branch main + npx prepare-release --branch 0.76-stable --dry-run +`); +} + +// --------------------------------------------------------------------------- +// Remote detection +// --------------------------------------------------------------------------- + +/** + * Normalize a git URL to a canonical form for comparison. + * + * Handles SSH (git@github.com:org/repo.git), HTTPS, with/without .git suffix. + * Output: lowercase "github.com/org/repo" + */ +function normalizeGitUrl(url: string): string { + let normalized = url; + + // Strip trailing .git + normalized = normalized.replace(/\.git$/, ''); + + // Convert SSH format: git@github.com:org/repo -> github.com/org/repo + normalized = normalized.replace(/^git@([^:]+):/, '$1/'); + + // Convert HTTPS format: https://github.com/org/repo -> github.com/org/repo + normalized = normalized.replace(/^https?:\/\//, ''); + + // Lowercase for case-insensitive comparison + normalized = normalized.toLowerCase(); + + // Strip trailing slash + normalized = normalized.replace(/\/$/, ''); + + return normalized; +} + +/** + * Extract "owner/repo" from a git URL for use with `gh --repo`. + * + * E.g. "git@github.com:microsoft/react-native-windows.git" + * -> "microsoft/react-native-windows" + */ +function extractGitHubRepo(url: string): string { + const normalized = normalizeGitUrl(url); + // normalized is like "github.com/owner/repo" + const match = normalized.match(/github\.com\/(.+)/); + if (!match) { + throw new Error(`Could not extract GitHub owner/repo from "${url}"`); + } + return match[1]; +} + +/** + * Detect which git remote matches the repository URL from package.json. + * + * Parses `git remote -v` output and matches against the normalized repo URL. + * On CI this is typically "origin"; on developer machines it may differ. + */ +async function detectRemote(git: GitRepo, repoUrl: string): Promise { + const canonical = normalizeGitUrl(repoUrl); + const remoteOutput = await git.remoteList(); + + for (const line of remoteOutput.split('\n')) { + // Lines: "origin\thttps://github.com/microsoft/react-native-windows.git (fetch)" + const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/); + if (!match) { + continue; + } + const remoteName = match[1]; + const remoteUrl = match[2]; + + if (normalizeGitUrl(remoteUrl) === canonical) { + return remoteName; + } + } + + throw new Error( + `Could not find a git remote matching "${repoUrl}". ` + + 'Run "git remote -v" and verify the repository URL in root package.json.', + ); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +(async () => { + // 1. Parse CLI arguments + const {values} = parseArgs({ + options: { + branch: {type: 'string'}, + 'dry-run': {type: 'boolean', default: false}, + help: {type: 'boolean', short: 'h', default: false}, + 'no-color': {type: 'boolean', default: false}, + }, + }); + + if (values.help) { + showHelp(); + process.exit(0); + } + + useColors = !values['no-color']; + + const targetBranch = values.branch; + if (!targetBranch) { + console.error(colorize('Error: --branch is required', ansi.red)); + showHelp(); + process.exit(1); + } + + const dryRun = values['dry-run']; + + if (dryRun) { + console.log(colorize('[DRY RUN MODE]', ansi.yellow)); + } + + try { + // 2. Find repo root + const repoRoot = await findRepoRoot(); + console.log(`${colorize('Repository root:', ansi.bright)} ${repoRoot}`); + + // 3. Read repository URL from root package.json + const rootPkgJsonPath = path.join(repoRoot, 'package.json'); + const rootPkgJson = JSON.parse(fs.readFileSync(rootPkgJsonPath, 'utf8')); + const repoUrl: string = rootPkgJson.repository?.url ?? ''; + + if (!repoUrl) { + throw new Error('Could not find repository.url in root package.json'); + } + console.log(`${colorize('Repository URL:', ansi.bright)} ${repoUrl}`); + + // 4. Detect git remote name + const git = new GitRepo(repoRoot); + const remoteName = await detectRemote(git, repoUrl); + console.log(`${colorize('Git remote:', ansi.bright)} ${remoteName}`); + + // 4b. Extract GitHub owner/repo for gh CLI --repo flag + const githubRepo = extractGitHubRepo(repoUrl); + console.log(`${colorize('GitHub repo:', ansi.bright)} ${githubRepo}`); + + // 5. Fetch from remote + console.log(colorize(`Fetching from ${remoteName}...`, ansi.dim)); + await git.fetch(remoteName); + + // 6. Check for pending change files + console.log(colorize('Checking for change files...', ansi.dim)); + if (!hasChangeFiles(repoRoot)) { + console.log( + colorize('No pending change files found. Nothing to do.', ansi.green), + ); + process.exit(0); + } + console.log(colorize('Found pending change files.', ansi.green)); + + // 7. Check for existing PR + const prBranch = `prepare-release/${targetBranch}`; + console.log( + colorize(`Looking for existing PR from ${prBranch}...`, ansi.dim), + ); + const existingPR = await findPR({ + head: prBranch, + cwd: repoRoot, + repo: githubRepo, + }); + + if (existingPR) { + console.log( + `${colorize('Found existing PR:', ansi.bright)} #${ + existingPR.number + } (${existingPR.url})`, + ); + } else { + console.log(colorize('No existing PR found. Will create one.', ansi.dim)); + } + + // 8. Save original branch so we can restore it when done + const originalBranch = await git.currentBranch(); + console.log(colorize(`Saving current branch: ${originalBranch}`, ansi.dim)); + + try { + // 9. Create/reset the prepare-release branch from target branch HEAD + console.log( + colorize( + `Creating branch ${prBranch} from ${remoteName}/${targetBranch}...`, + ansi.dim, + ), + ); + await git.checkoutNewBranch(prBranch, `${remoteName}/${targetBranch}`); + + // 10. Run beachball bump + console.log(colorize('Running beachball bump...', ansi.bright)); + await bumpVersions({ + targetBranch, + remote: remoteName, + cwd: repoRoot, + }); + + // 11. Check if beachball actually changed anything + const status = await git.statusPorcelain(); + if (!status) { + console.log( + colorize( + 'beachball bump made no changes. Nothing to commit.', + ansi.yellow, + ), + ); + process.exit(0); + } + + // 12. Collect bumped package info for PR description + // Parse changed package.json paths from git status --porcelain output + const changedFiles = status + .split('\n') + .map(line => line.trim().split(/\s+/).pop()!) + .filter(f => f.endsWith('package.json')); + + const bumpedPackages = collectBumpedPackages(changedFiles, repoRoot); + console.log(generateConsoleSummary(bumpedPackages)); + + // 13. Stage all + commit + const commitMessage = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`; + console.log(colorize(`Committing: "${commitMessage}"...`, ansi.dim)); + await git.stageAll(); + await git.commit(commitMessage); + + // 14. Force-push the branch + if (dryRun) { + console.log( + colorize( + `[DRY RUN] Would force-push ${prBranch} to ${remoteName}`, + ansi.yellow, + ), + ); + } else { + console.log( + colorize(`Force-pushing ${prBranch} to ${remoteName}...`, ansi.dim), + ); + await git.push(remoteName, prBranch, {force: true}); + + // Verify the branch landed on the remote + const lsRemoteOut = await git.lsRemote(remoteName, prBranch); + if (!lsRemoteOut) { + throw new Error( + `Push verification failed: branch "${prBranch}" not found on ` + + `"${remoteName}" after push. Check your push permissions.`, + ); + } + console.log( + colorize( + `Push verified: ${prBranch} exists on ${remoteName}`, + ansi.green, + ), + ); + } + + // 15. Create or update PR + const prTitle = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`; + const prBody = generatePRBody(targetBranch, bumpedPackages); + + if (existingPR) { + if (dryRun) { + console.log( + colorize( + `[DRY RUN] Would update PR #${existingPR.number}`, + ansi.yellow, + ), + ); + } else { + console.log( + colorize(`Updating PR #${existingPR.number}...`, ansi.dim), + ); + await updatePR({ + number: existingPR.number, + title: prTitle, + body: prBody, + cwd: repoRoot, + repo: githubRepo, + }); + console.log( + `${colorize('PR updated:', ansi.green)} ${existingPR.url}`, + ); + } + } else { + if (dryRun) { + console.log( + colorize(`[DRY RUN] Would create PR: "${prTitle}"`, ansi.yellow), + ); + } else { + console.log(colorize('Creating pull request...', ansi.dim)); + const newPR = await createPR({ + head: prBranch, + base: targetBranch, + title: prTitle, + body: prBody, + cwd: repoRoot, + repo: githubRepo, + }); + console.log(`${colorize('PR created:', ansi.green)} ${newPR.url}`); + } + } + + // 16. Done + console.log(''); + console.log(colorize('Done!', ansi.green + ansi.bright)); + + if (dryRun) { + console.log(colorize('\nPR body that would be used:', ansi.dim)); + console.log(prBody); + } + } finally { + // Always restore the original branch + if (originalBranch && originalBranch !== 'HEAD') { + console.log( + colorize(`Restoring original branch: ${originalBranch}`, ansi.dim), + ); + await git.checkout(originalBranch); + } + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`${colorize('Error:', ansi.red)} ${message}`); + process.exit(1); + } +})(); diff --git a/packages/@rnw-scripts/prepare-release/src/proc.ts b/packages/@rnw-scripts/prepare-release/src/proc.ts new file mode 100644 index 00000000000..da5a19913e2 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/proc.ts @@ -0,0 +1,120 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Async wrapper around Node.js child_process.spawn. + * Simplified from fork-sync/src/modules/proc.ts. + * + * @format + */ + +import {spawn as nodeSpawn} from 'child_process'; + +/** + * Error thrown when a spawned process exits with a non-zero code. + */ +export class ExecError extends Error { + readonly command: string; + readonly args: readonly string[]; + readonly cwd: string | undefined; + readonly exitCode: number | null; + readonly stderr: string; + + constructor(opts: { + command: string; + args: readonly string[]; + cwd?: string; + exitCode: number | null; + stderr: string; + }) { + const cmdStr = [opts.command, ...opts.args].join(' '); + super( + opts.stderr || + `Command failed with exit code ${opts.exitCode}: ${cmdStr}`, + ); + this.name = 'ExecError'; + this.command = opts.command; + this.args = opts.args; + this.cwd = opts.cwd; + this.exitCode = opts.exitCode; + this.stderr = opts.stderr; + } +} + +export interface SpawnOpts { + cwd?: string; + /** If set, return this value instead of throwing on non-zero exit */ + fallback?: string; + /** Extra environment variables (merged with process.env) */ + env?: Record; +} + +/** + * Spawn a command (no shell) and return its trimmed stdout. + * Throws ExecError on non-zero exit unless `fallback` is provided. + */ +export function spawn( + command: string, + args: readonly string[], + opts?: SpawnOpts, +): Promise { + return spawnImpl(command, [...args], opts, false); +} + +/** + * Execute a command string in a shell and return its trimmed stdout. + * Uses shell mode, needed for .cmd shims on Windows (npx, gh). + */ +export function exec(command: string, opts?: SpawnOpts): Promise { + return spawnImpl(command, [], opts, true); +} + +function spawnImpl( + command: string, + args: string[], + opts: SpawnOpts | undefined, + shell: boolean, +): Promise { + return new Promise((resolve, reject) => { + const child = nodeSpawn(command, args, { + cwd: opts?.cwd, + env: opts?.env ? {...process.env, ...opts.env} : undefined, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + shell, + }); + + const stdoutChunks: Buffer[] = []; + const stderrChunks: Buffer[] = []; + + child.stdout!.on('data', (chunk: Buffer) => stdoutChunks.push(chunk)); + child.stderr!.on('data', (chunk: Buffer) => stderrChunks.push(chunk)); + + child.on('error', err => { + reject(err); + }); + + child.on('close', exitCode => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trimEnd(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trimEnd(); + + if (exitCode !== 0) { + if (opts?.fallback !== undefined) { + resolve(opts.fallback); + } else { + reject( + new ExecError({ + command, + args, + cwd: opts?.cwd, + exitCode, + stderr, + }), + ); + } + } else { + resolve(stdout); + } + }); + }); +} diff --git a/packages/@rnw-scripts/prepare-release/src/releaseSummary.ts b/packages/@rnw-scripts/prepare-release/src/releaseSummary.ts new file mode 100644 index 00000000000..d368497b62c --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/releaseSummary.ts @@ -0,0 +1,139 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Parse bumped packages and generate PR description markdown. + * + * @format + */ + +import fs from '@react-native-windows/fs'; +import path from 'path'; + +export interface BumpedPackage { + name: string; + version: string; + comments: Array<{comment: string; author: string}>; +} + +/** + * Collect information about packages that were bumped by beachball. + * + * For each changed package.json, reads the new version and parses + * CHANGELOG.json for the latest changelog entry. + * + * @param changedPackageJsonPaths Relative paths to package.json files + * that were modified by beachball bump (from git status --porcelain) + * @param repoRoot The repository root directory + */ +export function collectBumpedPackages( + changedPackageJsonPaths: string[], + repoRoot: string, +): BumpedPackage[] { + const bumped: BumpedPackage[] = []; + + for (const relPath of changedPackageJsonPaths) { + const fullPath = path.join(repoRoot, relPath); + if (!fs.existsSync(fullPath)) { + continue; + } + + let pkgJson: {name?: string; version?: string}; + try { + pkgJson = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + } catch { + continue; + } + + const name = pkgJson.name; + const version = pkgJson.version; + if (!name || !version) { + continue; + } + + // Try to read CHANGELOG.json from the same directory + const pkgDir = path.dirname(fullPath); + const changelogPath = path.join(pkgDir, 'CHANGELOG.json'); + const comments: Array<{comment: string; author: string}> = []; + + if (fs.existsSync(changelogPath)) { + try { + const changelog = JSON.parse(fs.readFileSync(changelogPath, 'utf8')); + + // CHANGELOG.json structure: + // { entries: [{ version, comments: { : [{ comment, author }] } }] } + const latest = changelog.entries?.[0]; + if (latest && latest.version === version) { + for (const typeComments of Object.values(latest.comments || {})) { + for (const c of typeComments as Array<{ + comment: string; + author: string; + }>) { + comments.push({ + comment: c.comment || '', + author: c.author || '', + }); + } + } + } + } catch { + // If CHANGELOG.json is malformed, skip comments + } + } + + bumped.push({name, version, comments}); + } + + // Sort by package name for consistent output + bumped.sort((a, b) => a.name.localeCompare(b.name)); + return bumped; +} + +/** + * Generate the pull request body markdown. + */ +export function generatePRBody( + targetBranch: string, + packages: BumpedPackage[], +): string { + const lines: string[] = []; + + lines.push( + 'This PR was auto-generated by `prepare-release`. ' + + `When ready to release, merge this PR into \`${targetBranch}\`. ` + + 'If not ready yet, this PR will be updated as more changes merge.', + ); + lines.push(''); + lines.push(`## Packages to Release (${packages.length})`); + lines.push(''); + + for (const pkg of packages) { + lines.push(`### ${pkg.name}@${pkg.version}`); + if (pkg.comments.length > 0) { + for (const c of pkg.comments) { + lines.push(`- ${c.comment}`); + } + } else { + lines.push('- *(dependency update)*'); + } + lines.push(''); + } + + return lines.join('\n'); +} + +/** + * Generate a console-friendly summary of bumped packages. + */ +export function generateConsoleSummary(packages: BumpedPackage[]): string { + if (packages.length === 0) { + return 'No packages were bumped.'; + } + + const lines: string[] = []; + lines.push(`Bumped ${packages.length} package(s):`); + for (const pkg of packages) { + lines.push(` ${pkg.name} => ${pkg.version}`); + } + return lines.join('\n'); +} diff --git a/packages/@rnw-scripts/prepare-release/tsconfig.json b/packages/@rnw-scripts/prepare-release/tsconfig.json new file mode 100644 index 00000000000..c62faa78baf --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@rnw-scripts/ts-config", + "include": ["src"], + "exclude": ["node_modules"] +} From a5259e567dde07e4ee2ee5f4541bb57ef65e318a Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Mar 2026 17:16:44 -0800 Subject: [PATCH 2/2] Update year.lock --- yarn.lock | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/yarn.lock b/yarn.lock index 451ba1cae2a..1528eb13da5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1341,11 +1341,23 @@ dependencies: eslint-visitor-keys "^3.4.3" +"@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== +"@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + "@eslint/eslintrc@^2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" @@ -2162,6 +2174,34 @@ resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.11.4.tgz#7fb09506ee00a82989125cc03e8495204c8afc01" integrity sha512-Kf8h1AMnBo54b1fdiVylP2P/iFcZqzpMYcglC28EEFB1DEnOjsNr6Ucqc+3R9e91vHxEDnhZFbYDmAe79P2gjA== +"@react-native-windows/find-repo-root@^0.0.0-canary.99": + version "0.0.0-canary.99" + resolved "https://registry.yarnpkg.com/@react-native-windows/find-repo-root/-/find-repo-root-0.0.0-canary.99.tgz#9aba0d034c7bbe0de7108b38bb5e352ec8c6cbf2" + integrity sha512-ks88JuTE/EmV9etG0QpxXo96lgnYe5q5RjbtW0aboLy93djKVb+P95xrr7psk15TzcQGTM063iJrKVabRjLhNg== + dependencies: + "@react-native-windows/fs" "^0.0.0-canary.70" + find-up "^4.1.0" + minimatch "^10.0.3" + +"@react-native-windows/fs@^0.0.0-canary.70": + version "0.0.0-canary.70" + resolved "https://registry.yarnpkg.com/@react-native-windows/fs/-/fs-0.0.0-canary.70.tgz#53165cc8f0310be7aebb1bda669ff54ae2298ada" + integrity sha512-o4DY6n31140d+8ylusqzFYM69ReJ3J56i5vAZw3Pq0CtoELyloOSCHBUNmNFTZKnOeU+3RDZ2hH/nv4Vf8lstA== + dependencies: + graceful-fs "^4.2.8" + minimatch "^10.0.3" + +"@react-native-windows/package-utils@^0.0.0-canary.96": + version "0.0.0-canary.96" + resolved "https://registry.yarnpkg.com/@react-native-windows/package-utils/-/package-utils-0.0.0-canary.96.tgz#545e53dabd788203d6daa2c050df6cea3836ed0b" + integrity sha512-FQ8c0vxVhIVOjmru+8CPliEwhRx+WPDFcskf4eu/cSiucW3n1LmBGsZaLkae02FHOl2yioT9hW1L7X5YDj5xIA== + dependencies: + "@react-native-windows/find-repo-root" "^0.0.0-canary.99" + "@react-native-windows/fs" "^0.0.0-canary.70" + get-monorepo-packages "^1.2.0" + lodash "^4.17.15" + minimatch "^10.0.3" + "@react-native/assets-registry@0.81.6": version "0.81.6" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.81.6.tgz#4a9edfa209e4ef43adb372cbf481a7c4ff2e7342" @@ -2297,11 +2337,34 @@ eslint-plugin-react-hooks "^4.6.0" eslint-plugin-react-native "^4.0.0" +"@react-native/eslint-config@0.82.0-nightly-20250806-5936f29d6": + version "0.82.0-nightly-20250806-5936f29d6" + resolved "https://registry.yarnpkg.com/@react-native/eslint-config/-/eslint-config-0.82.0-nightly-20250806-5936f29d6.tgz#b10d372254735683891f16de1b430b89ea47abd5" + integrity sha512-3bBcXPGjwAGSIUYW3Z+fcyPOoiLbIXF+QAe7qO7hDMz7NxlA6bJ7njaMTmryJhTuWX382DXu/XkVV15GjAiMMQ== + dependencies: + "@babel/core" "^7.25.2" + "@babel/eslint-parser" "^7.25.1" + "@react-native/eslint-plugin" "0.82.0-nightly-20250806-5936f29d6" + "@typescript-eslint/eslint-plugin" "^8.36.0" + "@typescript-eslint/parser" "^8.36.0" + eslint-config-prettier "^8.5.0" + eslint-plugin-eslint-comments "^3.2.0" + eslint-plugin-ft-flow "^2.0.1" + eslint-plugin-jest "^27.9.0" + eslint-plugin-react "^7.30.1" + eslint-plugin-react-hooks "^5.2.0" + eslint-plugin-react-native "^4.0.0" + "@react-native/eslint-plugin@0.79.0-nightly-20250123-d1028885e": version "0.79.0-nightly-20250123-d1028885e" resolved "https://registry.yarnpkg.com/@react-native/eslint-plugin/-/eslint-plugin-0.79.0-nightly-20250123-d1028885e.tgz#7983608a83b724ab2f31d2e84e67fb5f9a7ad3b1" integrity sha512-vYrjxRh3wvL9MmmmsO52keyONK8O7CqayB1gvgSf/EeptcnnCpd0YdJ06NU54aB3zh6p+k4vkxHliNFqpBTFBg== +"@react-native/eslint-plugin@0.82.0-nightly-20250806-5936f29d6": + version "0.82.0-nightly-20250806-5936f29d6" + resolved "https://registry.yarnpkg.com/@react-native/eslint-plugin/-/eslint-plugin-0.82.0-nightly-20250806-5936f29d6.tgz#5800835a7c357066810b0452ebe7e4f4c955ea31" + integrity sha512-Xi6Wcy+VY6Vup69LfIQQpFX27UlcyTTvroHXkAUtxPPJcj3g5BwlVIo3koLWaOGadrXWFxnaSRmYDdpK4QY8Ag== + "@react-native/gradle-plugin@0.81.6": version "0.81.6" resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.81.6.tgz#d0c6d95bf6549a86c4cfbfd9317bc24717e791fb" @@ -2362,6 +2425,32 @@ invariant "^2.2.4" nullthrows "^1.1.1" +"@rnw-scripts/eslint-config@1.2.38": + version "1.2.38" + resolved "https://registry.yarnpkg.com/@rnw-scripts/eslint-config/-/eslint-config-1.2.38.tgz#1a0044fd8a4ddb538cd91efdb0cf6c83a25efe29" + integrity sha512-BdGhf0TWkhoL9HqgGQwVhpCOr4DrhtlDmF+tHLZu5nRC1aR9p/frM+BoJVlY3g2Vezu5lN513h0oU8O8b09H3w== + dependencies: + "@babel/core" "^7.25.2" + "@babel/eslint-parser" "^7.25.1" + "@microsoft/eslint-plugin-sdl" "^0.2.0" + "@react-native/eslint-config" "0.82.0-nightly-20250806-5936f29d6" + eslint-config-prettier "^8.5.0" + eslint-plugin-ft-flow "^2.0.1" + hermes-eslint "0.23.1" + +"@rnw-scripts/just-task@2.3.58": + version "2.3.58" + resolved "https://registry.yarnpkg.com/@rnw-scripts/just-task/-/just-task-2.3.58.tgz#a5e4e6192a218086f94f74dc0cc1abcf209185d7" + integrity sha512-tLyjF0xwrl0mJjc4ymRjbLvPpC06U0X56ctan71QHdO6MP9XPKGtI9rH4RiLrlST2Gc91VO15nqedODvkHN0lg== + dependencies: + "@octokit/rest" "^18.5.3" + "@rnw-scripts/jest-e2e-config" "1.4.12" + "@rnw-scripts/jest-unittest-config" "1.5.12" + depcheck "^1.4.1" + find-up "^4.1.0" + glob "^7.1.6" + just-scripts "^1.3.3" + "@rnx-kit/align-deps@^2.5.0": version "2.5.5" resolved "https://registry.yarnpkg.com/@rnx-kit/align-deps/-/align-deps-2.5.5.tgz#708b4fd65941699e1ef33c728d34bc568e648b73" @@ -3177,6 +3266,20 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" +"@typescript-eslint/eslint-plugin@^8.36.0": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz#b1ce606d87221daec571e293009675992f0aae76" + integrity sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.56.1" + "@typescript-eslint/type-utils" "8.56.1" + "@typescript-eslint/utils" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.4.0" + "@typescript-eslint/parser@7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.2.0.tgz#44356312aea8852a3a82deebdacd52ba614ec07a" @@ -3199,6 +3302,26 @@ "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" +"@typescript-eslint/parser@^8.36.0": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.56.1.tgz#21d13b3d456ffb08614c1d68bb9a4f8d9237cdc7" + integrity sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg== + dependencies: + "@typescript-eslint/scope-manager" "8.56.1" + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/typescript-estree" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.56.1.tgz#65c8d645f028b927bfc4928593b54e2ecd809244" + integrity sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.56.1" + "@typescript-eslint/types" "^8.56.1" + debug "^4.4.3" + "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" @@ -3223,6 +3346,19 @@ "@typescript-eslint/types" "7.2.0" "@typescript-eslint/visitor-keys" "7.2.0" +"@typescript-eslint/scope-manager@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz#254df93b5789a871351335dd23e20bc164060f24" + integrity sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w== + dependencies: + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + +"@typescript-eslint/tsconfig-utils@8.56.1", "@typescript-eslint/tsconfig-utils@^8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz#1afa830b0fada5865ddcabdc993b790114a879b7" + integrity sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ== + "@typescript-eslint/type-utils@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" @@ -3233,6 +3369,17 @@ debug "^4.3.4" ts-api-utils "^1.3.0" +"@typescript-eslint/type-utils@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz#7a6c4fabf225d674644931e004302cbbdd2f2e24" + integrity sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg== + dependencies: + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/typescript-estree" "8.56.1" + "@typescript-eslint/utils" "8.56.1" + debug "^4.4.3" + ts-api-utils "^2.4.0" + "@typescript-eslint/types@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" @@ -3248,6 +3395,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.2.0.tgz#0feb685f16de320e8520f13cca30779c8b7c403f" integrity sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA== +"@typescript-eslint/types@8.56.1", "@typescript-eslint/types@^8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.56.1.tgz#975e5942bf54895291337c91b9191f6eb0632ab9" + integrity sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw== + "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" @@ -3289,6 +3441,21 @@ semver "^7.5.4" ts-api-utils "^1.0.1" +"@typescript-eslint/typescript-estree@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz#3b9e57d8129a860c50864c42188f761bdef3eab0" + integrity sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg== + dependencies: + "@typescript-eslint/project-service" "8.56.1" + "@typescript-eslint/tsconfig-utils" "8.56.1" + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" + "@typescript-eslint/utils@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" @@ -3299,6 +3466,16 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/typescript-estree" "7.18.0" +"@typescript-eslint/utils@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.56.1.tgz#5a86acaf9f1b4c4a85a42effb217f73059f6deb7" + integrity sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.56.1" + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/typescript-estree" "8.56.1" + "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.47.1": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -3337,6 +3514,14 @@ "@typescript-eslint/types" "7.2.0" eslint-visitor-keys "^3.4.1" +"@typescript-eslint/visitor-keys@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz#50e03475c33a42d123dc99e63acf1841c0231f87" + integrity sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw== + dependencies: + "@typescript-eslint/types" "8.56.1" + eslint-visitor-keys "^5.0.0" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -4106,6 +4291,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + bare-events@^2.2.0, bare-events@^2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745" @@ -4218,6 +4408,13 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" +brace-expansion@^5.0.2: + version "5.0.4" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" + integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== + dependencies: + balanced-match "^4.0.2" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -5007,6 +5204,13 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d dependencies: ms "^2.1.3" +debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -5667,6 +5871,11 @@ eslint-plugin-react-hooks@^4.6.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== +eslint-plugin-react-hooks@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" + integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== + eslint-plugin-react-native-globals@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" @@ -5788,6 +5997,11 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + eslint@^8.19.0, eslint@^8.57.0: version "8.57.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" @@ -6067,6 +6281,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -6969,6 +7188,11 @@ ignore@^5.0.5, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + image-size@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.2.1.tgz#ee118aedfe666db1a6ee12bed5821cde3740276d" @@ -9193,6 +9417,13 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" +minimatch@^10.0.3, minimatch@^10.2.2: + version "10.2.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== + dependencies: + brace-expansion "^5.0.2" + minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -9972,6 +10203,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" @@ -11512,6 +11748,14 @@ tinybench@^3.1.0: resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-3.1.0.tgz#ec68451ff05233cf3de12c46f39f06011897109a" integrity sha512-Km+oMh2xqNCxuyoUsqbRmHgFSd8sATh7v7xreP+kHN6x67w28Pawr83WmBxcaORvxkc0Ex6zgqK951yBnTFaaQ== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -11563,6 +11807,11 @@ ts-api-utils@^1.0.1, ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.0.tgz#709c6f2076e511a81557f3d07a0cbd566ae8195c" integrity sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ== +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== + ts-jest@^29.0.3: version "29.4.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.5.tgz#a6b0dc401e521515d5342234be87f1ca96390a6f"