From f7191bb0c14c8a5acdf023e4a7fa2f5a8f2700d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Thu, 7 May 2026 00:10:19 -0700 Subject: [PATCH 01/23] Upgrade to PowerShell 7 (#16075) * Ran NuGetRestoreForceEvaluateAllSolutions.ps1 * Define installNuGetPackages task; Install pwsh 7.6.1 * Replace powershell.exe with pwsh.exe * Change files * CI: Ensure NuGet is availabe before `yarn install` * Add NuGetAuthenticate before yarn install * Do not download PowerShell on CI/CD environments * Fix just-task JS syntax * Add dotnet-tools manifest * Fix pwsh.exe tool path * Resolve dotnet-tools.json path * Use dotnet for `nuget locals` * Use PWSH in nuget-restore-task.js * Break dependency loop in just-task * Update other ADO tasks * Update @react-native-windows/automation * Rename findPwsh as findPowerShell * Drop powershell export from commandWithProgress * Change files * Add README for find-dotnet-tools * Add README to package.json * Adjust newline * Run task `installDotnetTools` only when not in CI * Install .NET tools in CI * Install .NET on prepare-js-env * Do not install .NET tools on CI * Quote powershell path * fix yarn lint * Ran NuGetRestoreForceEvaluateAllSolutions.ps1 * Restore deleted lock files * Add missing dependency * Use -Command for compatible PWSH calls * Use -Command pwsh argument * Correct runPowerShellScriptFunction cmd escaping * Quote pwsh command in runPowerShellScriptFunction * Remove quotes * Quote remaining findPowerShell() calls for execSync * Add temporary diagnostic commands * Diagnostics * Diagnostics * Diagnostics * Add runPowerShellScriptFunction argument to delegate Appx-related commands to Windows PowerShell * Remove diagnostics steps * Use execFileSync instead of execSync in deploy.ts * Remove querystrings from FS paths * Suppress warning when using `-UseWindowsPowerShell` * Revert "Remove querystrings from FS paths" This reverts commit e1f3cfdf0520f916e4f7fe6ac62a7eebba3bb51a. * Use pipe semantics for Get-AppxPackage in resolveAppName * Hard-fail if PowerShell 7 cannot be found. Do not fall back to Windows PowerShell (powershell.exe). PowerShell code may not be backwards compatible. * Improve missing pwsh.exe error message * Add package resolution pipeline test * UseWindowsPowerShell for package resolution test * Try to read package Microsoft.PrintDialog * Replace stderr redirection with -WarningAction * Replace stderr redirection with -WarningAction * Print sample app names on appWindow error * Add more diagnostics * Add missing -WarningAction SilentlyContinue * Revert "Add more diagnostics" This reverts commit 8c10bc4d1abd2afc30240d06eb7cf8a113422a6d. * Revert "Print sample app names on appWindow error" This reverts commit 6e70148a35e50d4bd9c62f50908e2b3855ef6dce. * Remove CI probing tasks * Revert "Use pipe semantics for Get-AppxPackage in resolveAppName" This reverts commit 6c46c73413af274b4535e94a327ed1d3202dc2ed. Co-authored-by: Copilot * Remove unused escaping logic * Re-introduce deleted .lock.json --------- Co-authored-by: Copilot --- .ado/jobs/e2e-test.yml | 15 + .ado/scripts/build.js | 2 +- .ado/templates/install-SDK.yml | 1 + ...-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json | 7 + ...-d0909d53-2984-4eff-bcf6-fe1702c03277.json | 7 + ...-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json | 7 + ...-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json | 7 + .../automation/package.json | 2 +- .../automation/src/AutomationEnvironment.ts | 13 +- .../@react-native-windows/cli/package.json | 2 +- .../src/commands/healthCheck/healthChecks.ts | 6 +- .../cli/src/e2etest/healthChecks.test.ts | 6 +- .../cli/src/utils/commandWithProgress.ts | 15 +- .../cli/src/utils/deploy.ts | 30 +- .../cli/src/utils/msbuildtools.ts | 4 +- .../find-dotnet-tools/.eslintrc.js | 4 + .../find-dotnet-tools/.gitignore | 2 + .../find-dotnet-tools/README.md | 50 +++ .../find-dotnet-tools/package.json | 52 +++ .../find-dotnet-tools/src/findDotnetTools.ts | 70 ++++ .../find-dotnet-tools/tsconfig.json | 5 + .../just-task/nuget-restore-task.js | 5 +- vnext/dotnet-tools.json | 12 + vnext/just-task.js | 16 +- yarn.lock | 374 ++++++++++++++++++ 25 files changed, 687 insertions(+), 27 deletions(-) create mode 100644 change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json create mode 100644 change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json create mode 100644 change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json create mode 100644 change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json create mode 100644 packages/@react-native-windows/find-dotnet-tools/.eslintrc.js create mode 100644 packages/@react-native-windows/find-dotnet-tools/.gitignore create mode 100644 packages/@react-native-windows/find-dotnet-tools/README.md create mode 100644 packages/@react-native-windows/find-dotnet-tools/package.json create mode 100644 packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts create mode 100644 packages/@react-native-windows/find-dotnet-tools/tsconfig.json create mode 100644 vnext/dotnet-tools.json diff --git a/.ado/jobs/e2e-test.yml b/.ado/jobs/e2e-test.yml index d6c9d3b3e18..93d2930689d 100644 --- a/.ado/jobs/e2e-test.yml +++ b/.ado/jobs/e2e-test.yml @@ -320,6 +320,21 @@ jobs: condition: and(failed(), eq(variables.StartedFabricTests, 'true')) continueOnError: true + - pwsh: | + if (Test-Path "packages/e2e-test-app-fabric/test/__image_snapshots__/__diff_output__") { + Write-Host "##vso[task.setvariable variable=DiffOutputExists]true" + } + displayName: Check for image diff output folder + condition: failed() + + - task: CopyFiles@2 + displayName: Copy Fabric image diffs + inputs: + sourceFolder: packages/e2e-test-app-fabric/test/__image_snapshots__/__diff_output__ + targetFolder: $(Build.StagingDirectory)/snapshots-image-diffs + contents: "**" + condition: and(failed(), eq(variables.DiffOutputExists, 'true')) + - script: npx jest --clearCache displayName: clear jest cache workingDirectory: packages/e2e-test-app-fabric diff --git a/.ado/scripts/build.js b/.ado/scripts/build.js index 47df62d2c65..db46551eba9 100644 --- a/.ado/scripts/build.js +++ b/.ado/scripts/build.js @@ -101,7 +101,7 @@ function ensureNuGet(toolsPath) { ensureDir(toolsPath); console.log(`Downloading nuget.exe to: ${localNuGet}`); execSync( - `powershell.exe -NoLogo -NoProfile -Command ` + + `pwsh.exe -NoLogo -NoProfile -Command ` + `"[Net.ServicePointManager]::SecurityProtocol = ` + `[Net.SecurityProtocolType]::Tls12; ` + `Invoke-WebRequest -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' ` + diff --git a/.ado/templates/install-SDK.yml b/.ado/templates/install-SDK.yml index ef301a278dd..18fda6f4c47 100644 --- a/.ado/templates/install-SDK.yml +++ b/.ado/templates/install-SDK.yml @@ -7,5 +7,6 @@ steps: targetType: filePath filePath: vnext\Scripts\Install-WindowsSdkISO.ps1 arguments: ${{ parameters.sdkVersion }} + pwsh: true displayName: 'Install Insider SDK (${{ parameters.sdkVersion }})' condition: ne('', '${{ parameters.sdkVersion }}') diff --git a/change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json b/change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json new file mode 100644 index 00000000000..44cad7b1964 --- /dev/null +++ b/change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Migrate to PowerShell 7", + "packageName": "@react-native-windows/automation", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json b/change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json new file mode 100644 index 00000000000..46065e97e3a --- /dev/null +++ b/change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to PowerShell 7", + "packageName": "@react-native-windows/cli", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json b/change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json new file mode 100644 index 00000000000..621e68e6ec3 --- /dev/null +++ b/change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Migrate to PowerShell 7", + "packageName": "@react-native-windows/find-dotnet-tools", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json b/change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json new file mode 100644 index 00000000000..f1e3abb1df6 --- /dev/null +++ b/change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to PowerShell 7", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/automation/package.json b/packages/@react-native-windows/automation/package.json index dc36a805281..c09bcb6e7fd 100644 --- a/packages/@react-native-windows/automation/package.json +++ b/packages/@react-native-windows/automation/package.json @@ -63,4 +63,4 @@ "engines": { "node": ">= 22" } -} +} \ No newline at end of file diff --git a/packages/@react-native-windows/automation/src/AutomationEnvironment.ts b/packages/@react-native-windows/automation/src/AutomationEnvironment.ts index 9088328a9d6..2306bfa0ec6 100644 --- a/packages/@react-native-windows/automation/src/AutomationEnvironment.ts +++ b/packages/@react-native-windows/automation/src/AutomationEnvironment.ts @@ -8,6 +8,7 @@ import chalk from 'chalk'; import {spawnSync, spawn, ChildProcess} from 'child_process'; import fs from '@react-native-windows/fs'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import path from 'path'; import readlineSync from 'readline-sync'; @@ -250,7 +251,7 @@ export default class AutomationEnvironment extends NodeEnvironment { if (this.breakOnStart) { readlineSync.question( chalk.bold.yellow('Breaking before tests start\n') + - 'Press Enter to resume...', + 'Press Enter to resume...', ); } @@ -335,8 +336,14 @@ function resolveAppName(appName: string): string { } try { - const packageFamilyName = spawnSync('powershell', [ - `(Get-AppxPackage -Name ${appName}).PackageFamilyName`, + const useAppxCompatibility = !!process.env.TF_BUILD; + const packageFamilyNameCommand = useAppxCompatibility + ? `& { Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; (Get-AppxPackage -Name '${appName}').PackageFamilyName }` + : `(Get-AppxPackage -Name '${appName}').PackageFamilyName`; + const packageFamilyName = spawnSync(findPowerShell(), [ + '-NoProfile', + '-Command', + packageFamilyNameCommand, ]) .stdout.toString() .trim(); diff --git a/packages/@react-native-windows/cli/package.json b/packages/@react-native-windows/cli/package.json index 7287cce93f1..ea9c4d594d3 100644 --- a/packages/@react-native-windows/cli/package.json +++ b/packages/@react-native-windows/cli/package.json @@ -88,4 +88,4 @@ "engines": { "node": ">= 22" } -} +} \ No newline at end of file diff --git a/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts b/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts index 90a4a11a1df..74d4797811a 100644 --- a/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts +++ b/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts @@ -14,9 +14,11 @@ import type { HealthCheckCategory, HealthCheckInterface, } from '@react-native-community/cli-doctor/build/types'; -import {powershell} from '../../utils/commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import {HealthCheckList} from './healthCheckList'; +const powershell = findPowerShell(); + export function getHealthChecks(): HealthCheckCategory[] | undefined { // #8471: There are known cases where the dependencies script will error out. // Fail gracefully if that happens in the meantime. @@ -76,7 +78,7 @@ function getHealthChecksUnsafe(): HealthCheckCategory[] | undefined { }; }, runAutomaticFix: async ({loader, logManualInstallation}) => { - const command = `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`; + const command = `"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`; try { const {exitCode} = await execa(command, {stdio: 'inherit'}); if (exitCode) { diff --git a/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts b/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts index ad0a17edfb2..98d2be3a9f8 100644 --- a/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts +++ b/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts @@ -6,9 +6,11 @@ import {execSync} from 'child_process'; import path from 'path'; -import {powershell} from '../utils/commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import {HealthCheckList} from '../commands/healthCheck/healthCheckList'; +const powershell = findPowerShell(); + test('Verify list of health checks aligns with rnw-dependencies', async () => { const rnwDepScriptPath = path.join( path.dirname( @@ -20,7 +22,7 @@ test('Verify list of health checks aligns with rnw-dependencies', async () => { ); const rnwDeps = execSync( - `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -ListChecks`, + `"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -ListChecks`, {stdio: 'pipe'}, ); const deps = rnwDeps.toString().trim().split('\n'); diff --git a/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts b/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts index 74e8bec3d0c..9ee9fe56113 100644 --- a/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts +++ b/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts @@ -14,6 +14,7 @@ import { CodedErrors, CodedErrorType, } from '@react-native-windows/telemetry'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; function setSpinnerText(spinner: ora.Ora, prefix: string, text: string) { text = prefix + spinnerString(text); @@ -47,7 +48,7 @@ export function newSpinner(text: string) { return ora(options).start(); } -export const powershell = `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`; +const powershell = findPowerShell(); export async function runPowerShellScriptFunction( taskDescription: string, @@ -55,10 +56,15 @@ export async function runPowerShellScriptFunction( funcName: string, verbose: boolean, errorCategory: CodedErrorType, + useAppxCompatibility = false, ) { try { const printException = verbose ? '$_;' : ''; - const importScript = script ? `Import-Module "${script}"; ` : ''; + const importAppx = useAppxCompatibility + ? 'Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; ' + : ''; + const importScript = script ? `Import-Module '${script}'; ` : ''; + const powershellCommand = `${importAppx}${importScript}try { ${funcName} -ErrorAction Stop; $lec = $LASTEXITCODE; } catch { $lec = 1; ${printException} }; exit $lec`; await commandWithProgress( newSpinner(taskDescription), taskDescription, @@ -67,7 +73,8 @@ export async function runPowerShellScriptFunction( '-NoProfile', '-ExecutionPolicy', 'RemoteSigned', - `${importScript}try { ${funcName} -ErrorAction Stop; $lec = $LASTEXITCODE; } catch { $lec = 1; ${printException} }; exit $lec`, + '-Command', + `&{${powershellCommand}}`, ], verbose, errorCategory, @@ -88,7 +95,7 @@ export function commandWithProgress( errorCategory: CodedErrorType, ) { return new Promise((resolve, reject) => { - const spawnOptions: SpawnOptions = verbose ? {stdio: 'inherit'} : {}; + const spawnOptions: SpawnOptions = verbose ? { stdio: 'inherit' } : {}; if (verbose) { spinner.stop(); diff --git a/packages/@react-native-windows/cli/src/utils/deploy.ts b/packages/@react-native-windows/cli/src/utils/deploy.ts index 39395245d74..4335d166c36 100644 --- a/packages/@react-native-windows/cli/src/utils/deploy.ts +++ b/packages/@react-native-windows/cli/src/utils/deploy.ts @@ -4,7 +4,7 @@ * @format */ -import {spawn, execSync, SpawnOptions} from 'child_process'; +import {spawn, execFileSync, SpawnOptions} from 'child_process'; import fs from '@react-native-windows/fs'; import http from 'http'; import path from 'path'; @@ -19,8 +19,8 @@ import { newSpinner, commandWithProgress, runPowerShellScriptFunction, - powershell, } from './commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import * as build from './build'; import { BuildConfig, @@ -183,9 +183,12 @@ function getWindowsStoreAppUtils(options: RunWindowsOptions) { 'powershell', 'WindowsStoreAppUtils.psm1', ); - execSync( - `${powershell} -NoProfile Unblock-File '${windowsStoreAppUtilsPath}'`, - ); + const powershell = findPowerShell(); + execFileSync(powershell, [ + '-NoProfile', + '-Command', + `& { Unblock-File '${windowsStoreAppUtilsPath}' }`, + ]); popd(); return windowsStoreAppUtilsPath; } @@ -359,6 +362,7 @@ export async function deployToDesktop( config: Config, buildTools: MSBuildTools, ) { + const useAppxCompatibility = !!process.env.TF_BUILD; const windowsConfig: Partial | undefined = config.project.windows; const slnFile = @@ -391,6 +395,7 @@ export async function deployToDesktop( 'EnableDevMode', verbose, 'EnableDevModeFailure', + useAppxCompatibility, ); const appPackageFolder = getAppPackage(options, projectName); @@ -403,6 +408,7 @@ export async function deployToDesktop( `Uninstall-App ${appName}`, verbose, 'RemoveOldAppVersionFailure', + useAppxCompatibility, ); const script = glob.sync( @@ -415,6 +421,7 @@ export async function deployToDesktop( `Install-App "${script}" -Force`, verbose, 'InstallAppFailure', + useAppxCompatibility, ); } else { // Deploy from layout @@ -442,6 +449,7 @@ export async function deployToDesktop( `Install-AppDependencies ${appxManifestPath} ${appPackageFolder} ${options.arch}`, verbose, 'InstallAppDependenciesFailure', + useAppxCompatibility, ); await build.buildSolution( buildTools, @@ -456,9 +464,14 @@ export async function deployToDesktop( } } - const appFamilyName = execSync( - `${powershell} -NoProfile -c $(Get-AppxPackage -Name ${appName}).PackageFamilyName`, - ) + const appFamilyNameCommand = useAppxCompatibility + ? `& { Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; (Get-AppxPackage -Name '${appName}').PackageFamilyName }` + : `(Get-AppxPackage -Name '${appName}').PackageFamilyName`; + const appFamilyName = execFileSync(findPowerShell(), [ + '-NoProfile', + '-Command', + appFamilyNameCommand, + ]) .toString() .trim(); @@ -488,6 +501,7 @@ export async function deployToDesktop( `Start-Locally ${appName} ${args}`, verbose, 'AppStartupFailure', + useAppxCompatibility, ); } else { newInfo('Skip the step to start the app'); diff --git a/packages/@react-native-windows/cli/src/utils/msbuildtools.ts b/packages/@react-native-windows/cli/src/utils/msbuildtools.ts index 479fbed6974..859ef8c28e9 100644 --- a/packages/@react-native-windows/cli/src/utils/msbuildtools.ts +++ b/packages/@react-native-windows/cli/src/utils/msbuildtools.ts @@ -18,8 +18,8 @@ import { newSpinner, newSuccess, newError, - powershell, } from './commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import {execSync} from 'child_process'; import {BuildArch, BuildConfig} from '../commands/runWindows/runWindowsOptions'; import {findLatestVsInstall} from './vsInstalls'; @@ -317,7 +317,7 @@ export default class MSBuildTools { 'Eval-MsBuildProperties.ps1', ); - let command = `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${msbuildEvalScriptPath}" -SolutionFile '${solutionFile}' -ProjectFile '${projectFile}' -MSBuildPath '${this.msbuildPath()}'`; + let command = `"${findPowerShell()}" -ExecutionPolicy Unrestricted -NoProfile "${msbuildEvalScriptPath}" -SolutionFile '${solutionFile}' -ProjectFile '${projectFile}' -MSBuildPath '${this.msbuildPath()}'`; if (propertyNames && propertyNames.length > 0) { command += ` -PropertyNames '${propertyNames.join(',')}'`; diff --git a/packages/@react-native-windows/find-dotnet-tools/.eslintrc.js b/packages/@react-native-windows/find-dotnet-tools/.eslintrc.js new file mode 100644 index 00000000000..35e0d115126 --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['@rnw-scripts'], + parserOptions: {tsconfigRootDir : __dirname}, +}; diff --git a/packages/@react-native-windows/find-dotnet-tools/.gitignore b/packages/@react-native-windows/find-dotnet-tools/.gitignore new file mode 100644 index 00000000000..f42efbb9f7c --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/.gitignore @@ -0,0 +1,2 @@ +lib/ +lib-commonjs/ diff --git a/packages/@react-native-windows/find-dotnet-tools/README.md b/packages/@react-native-windows/find-dotnet-tools/README.md new file mode 100644 index 00000000000..f643400da11 --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/README.md @@ -0,0 +1,50 @@ +# @react-native-windows/find-dotnet-tools + +Helpers to locate .NET-based tools (e.g. PowerShell) restored via `dotnet tool restore` or +available on PATH. + +Used to resolve tool paths consistently across local development and CI +environments. + +## Usage + +Add the package as a dependency: + +```json +{ + "dependencies": { + "@react-native-windows/find-dotnet-tools": "" + } +} +``` + +### findPowerShell + +Locates a PowerShell executable by checking, in order: + +1. A `dotnet-tool`-restored copy of `pwsh.exe` (skipped in CI builds) +2. `pwsh.exe` on the system PATH + +Throws an error if `pwsh.exe` cannot be found. + +```js +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; + +const pwsh = findPowerShell(); +// e.g. "C:\\Users\\user\\.nuget\\packages\\PowerShell\\7.6.1\\tools\\net10.0\\any\\win\\pwsh.exe" +``` + +### getNugetGlobalPackagesFolder + +Returns the path to the global NuGet packages folder by checking, in order: + +1. The `NUGET_PACKAGES` environment variable +2. The output of `dotnet nuget locals global-packages --list` +3. The default `~/.nuget/packages` location + +```js +import {getNugetGlobalPackagesFolder} from '@react-native-windows/find-dotnet-tools'; + +const packagesDir = getNugetGlobalPackagesFolder(); +// e.g. "C:\\Users\\user\\.nuget\\packages" +``` diff --git a/packages/@react-native-windows/find-dotnet-tools/package.json b/packages/@react-native-windows/find-dotnet-tools/package.json new file mode 100644 index 00000000000..77901e68a1c --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/package.json @@ -0,0 +1,52 @@ +{ + "name": "@react-native-windows/find-dotnet-tools", + "description": "Helpers to locate .NET-based tools (e.g. pwsh) restored via NuGet or available on PATH.", + "version": "0.0.0-canary.1", + "license": "MIT", + "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/findDotnetTools.js", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/react-native-windows", + "directory": "packages/@react-native-windows/find-dotnet-tools" + }, + "dependencies": { + "@react-native-windows/fs": "^0.0.0-canary.72" + }, + "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": "^8.36.0", + "@typescript-eslint/parser": "^8.36.0", + "eslint": "^8.19.0", + "prettier": "^3.6.2", + "typescript": "5.0.4" + }, + "beachball": { + "defaultNpmTag": "canary", + "disallowedChangeTypes": [ + "major", + "minor", + "patch", + "premajor", + "preminor", + "prepatch" + ] + }, + "promoteRelease": true, + "files": [ + "lib-commonjs", + "README.md" + ], + "engines": { + "node": ">= 22" + } +} diff --git a/packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts b/packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts new file mode 100644 index 00000000000..d684656bee4 --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts @@ -0,0 +1,70 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +import {execSync} from 'child_process'; +import fs from '@react-native-windows/fs'; +import os from 'os'; +import path from 'path'; + +/** + * Returns the path to the global NuGet packages folder, checking (in order): + * 1. The NUGET_PACKAGES environment variable + * 2. The `dotnet nuget locals` command output + * 3. The default ~/.nuget/packages location + */ +export function getNugetGlobalPackagesFolder(): string { + if (process.env.NUGET_PACKAGES) { + return process.env.NUGET_PACKAGES; + } + try { + const output = execSync('dotnet.exe nuget locals global-packages --list', { + encoding: 'utf8', + }).trim(); + const match = output.match(/global-packages:\s*(.+)/i); + if (match) { + return match[1].trim(); + } + } catch {} + return path.join(os.homedir(), '.nuget', 'packages'); +} + +/** + * Locates a PowerShell executable, checking (in order): + * 1. A NuGet-restored copy of pwsh (skipped in CI builds) + * 2. pwsh.exe on the system PATH + * + * Throws if no pwsh.exe can be located. + */ +export function findPowerShell(): string { + // Build agents already have PowerShell (pwsh) installed + if (!process.env.TF_BUILD) { + const nugetPackages = getNugetGlobalPackagesFolder(); + const nugetPwsh = path.join( + nugetPackages, + 'PowerShell', + '7.6.1', + 'tools', + 'net10.0', + 'any', + 'win', + 'pwsh.exe', + ); + if (fs.existsSync(nugetPwsh)) { + return nugetPwsh; + } + } + + try { + const found = execSync('where pwsh.exe', {encoding: 'utf8'}).trim(); + if (found) { + return found.split(/\r?\n/)[0]; + } + } catch {} + + throw new Error( + 'Unable to find pwsh.exe. It should have been made available by `yarn install`.', + ); +} diff --git a/packages/@react-native-windows/find-dotnet-tools/tsconfig.json b/packages/@react-native-windows/find-dotnet-tools/tsconfig.json new file mode 100644 index 00000000000..c62faa78baf --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@rnw-scripts/ts-config", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/@rnw-scripts/just-task/nuget-restore-task.js b/packages/@rnw-scripts/just-task/nuget-restore-task.js index 0e2e66546cb..46cedea07d2 100644 --- a/packages/@rnw-scripts/just-task/nuget-restore-task.js +++ b/packages/@rnw-scripts/just-task/nuget-restore-task.js @@ -9,6 +9,7 @@ const fs = require('fs'); const path = require('path'); const {execSync, spawnSync} = require('child_process'); const {task} = require('just-scripts'); +const {findPowerShell} = require('@react-native-windows/find-dotnet-tools'); function registerNuGetRestoreTask(options) { const config = normalizeOptions(options); @@ -51,12 +52,14 @@ function executeNuGetRestore(config) { `Restoring NuGet packages (log: ${path.relative(process.cwd(), logPath)})`, ); + const powershell = findPowerShell(); + const scriptArgs = config.scriptArguments.length ? ` ${config.scriptArguments.join(' ')}` : ''; const restoreCommand = `call ${quote( vsDevCmd, - )} && powershell -NoProfile -ExecutionPolicy Bypass -File ${quote( + )} && "${powershell}" -NoProfile -ExecutionPolicy Bypass -File ${quote( config.scriptPath, )}${scriptArgs}`; const wrappedCommand = `${restoreCommand}`; diff --git a/vnext/dotnet-tools.json b/vnext/dotnet-tools.json new file mode 100644 index 00000000000..2468cbda2de --- /dev/null +++ b/vnext/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "powershell": { + "version": "7.6.1", + "commands": [ + "pwsh" + ] + } + } +} \ No newline at end of file diff --git a/vnext/just-task.js b/vnext/just-task.js index e3fb44c76b1..e10779044fb 100644 --- a/vnext/just-task.js +++ b/vnext/just-task.js @@ -25,6 +25,7 @@ const fs = require('fs'); const { registerNuGetRestoreTask, } = require('@rnw-scripts/just-task/nuget-restore-task'); +const {findPowerShell} = require('@react-native-windows/find-dotnet-tools'); option('production'); option('clean'); @@ -44,11 +45,12 @@ function codegen(test) { ); } +const powershell = findPowerShell(); + function layoutMSRNCxx() { if (require('os').platform() === 'win32') { - const powershell = `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`; execSync( - `${powershell} -NoProfile .\\Scripts\\Tfs\\Layout-MSRN-Headers.ps1 -GenerateLocalCxx`, + `"${powershell}" -NoProfile .\\Scripts\\Tfs\\Layout-MSRN-Headers.ps1 -GenerateLocalCxx`, { env: process.env, }, @@ -84,12 +86,22 @@ registerNuGetRestoreTask({ scriptArguments: ['-SkipLockDeletion'], }); +function installDotnetToolsTask() { + execSync( + `dotnet tool restore --tool-manifest ${path.resolve(__dirname, 'dotnet-tools.json')}`, + {env: process.env}, + ); +} + +task('installDotnetTools', installDotnetToolsTask); + task( 'build', series( condition('clean', () => argv().clean), 'copyRNLibraries', 'copyReadmeAndLicenseFromRoot', + condition('installDotnetTools', () => !process.env.TF_BUILD), 'layoutMSRNCxx', 'compileTsPlatformOverrides', 'restoreNuGetPackages', diff --git a/yarn.lock b/yarn.lock index b8b5e89eaa4..981538c5edf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2627,16 +2627,178 @@ resolved "https://registry.yarnpkg.com/@react-native-windows/fs/-/fs-0.0.0-canary.71.tgz#20299b596f90f511f9ebddf62b56d2275430d57a" integrity sha512-a+MdjKRl3X9JlTQv+CO7pIdPW/8geqG37Urc4qXPgGFegDndb05XOMvR4z1qmQgKvMPDfgCbhqUFLbYXmZxoAw== dependencies: +<<<<<<< HEAD graceful-fs "^4.2.8" minimatch "^10.0.3" +||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) + "@jest/create-cache-key-function": "npm:^29.2.1" + "@jest/environment": "npm:^29.3.0" + "@jest/types": "npm:^29.2.1" + "@react-native-windows/automation-channel": "npm:0.0.0-canary.1052" + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/jest": "npm:^29.2.2" + "@types/node": "npm:^22.14.0" + "@types/readline-sync": "npm:^1.4.4" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + chalk: "npm:^4.1.2" + eslint: "npm:^8.19.0" + prettier: "npm:^3.6.2" + readline-sync: "npm:1.4.10" + typescript: "npm:5.0.4" + webdriverio: "npm:^6.9.0" + peerDependencies: + jest: ">=29.0.3" + jest-environment-node: ">=29.2.2" + languageName: unknown + linkType: soft +======= + "@jest/create-cache-key-function": "npm:^29.2.1" + "@jest/environment": "npm:^29.3.0" + "@jest/types": "npm:^29.2.1" + "@react-native-windows/automation-channel": "npm:0.0.0-canary.1052" + "@react-native-windows/find-dotnet-tools": "npm:0.0.0-canary.1" + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/jest": "npm:^29.2.2" + "@types/node": "npm:^22.14.0" + "@types/readline-sync": "npm:^1.4.4" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + chalk: "npm:^4.1.2" + eslint: "npm:^8.19.0" + prettier: "npm:^3.6.2" + readline-sync: "npm:1.4.10" + typescript: "npm:5.0.4" + webdriverio: "npm:^6.9.0" + peerDependencies: + jest: ">=29.0.3" + jest-environment-node: ">=29.2.2" + languageName: unknown + linkType: soft +>>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) "@react-native-windows/fs@^0.0.0-canary.72": version "0.0.0-canary.72" resolved "https://registry.yarnpkg.com/@react-native-windows/fs/-/fs-0.0.0-canary.72.tgz#36f877226a14c86c95739c5ca69b132ba8b819ff" integrity sha512-zM+VUo/hS0ckQ5RNcce2YDIMawEbcyxU8dZTBOTfUrURS5olkIR8ALldNsNP10IL8mU7VVNexH03+A06I+wA3g== dependencies: +<<<<<<< HEAD graceful-fs "^4.2.8" minimatch "^10.0.3" +||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) + "@babel/core": "npm:^7.25.2" + "@react-native-community/cli-doctor": "npm:20.0.0" + "@react-native-community/cli-types": "npm:20.0.0" + "@react-native-windows/codegen": "npm:0.0.0-canary.133" + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@react-native-windows/package-utils": "npm:^0.0.0-canary.98" + "@react-native-windows/telemetry": "npm:^0.0.0-canary.133" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/jest-unittest-config": "npm:1.5.12" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/chalk": "npm:^2.2.0" + "@types/jest": "npm:^29.2.2" + "@types/lodash": "npm:^4.14.168" + "@types/mustache": "npm:^4.1.1" + "@types/node": "npm:^22.14.0" + "@types/ora": "npm:^3.2.0" + "@types/prompts": "npm:2.0.10" + "@types/semver": "npm:^7.3.3" + "@types/shelljs": "npm:^0.8.8" + "@types/xml-parser": "npm:^1.2.29" + "@types/xmldom": "npm:^0.1.30" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + "@xmldom/xmldom": "npm:^0.7.7" + babel-jest: "npm:^29.6.3" + chalk: "npm:^4.1.0" + cli-spinners: "npm:^2.2.0" + envinfo: "npm:^7.5.0" + eslint: "npm:^8.19.0" + execa: "npm:^5.0.0" + find-up: "npm:^4.1.0" + glob: "npm:^7.1.1" + jest: "npm:^29.7.0" + lodash: "npm:^4.17.15" + mustache: "npm:^4.0.1" + ora: "npm:^3.4.0" + prettier: "npm:^3.6.2" + prompts: "npm:^2.4.1" + react: "npm:19.2.3" + react-native: "npm:0.85.0-nightly-20260114-f15985f4f" + semver: "npm:^7.3.2" + shelljs: "npm:^0.8.4" + typescript: "npm:5.0.4" + username: "npm:^5.1.0" + xml-formatter: "npm:^2.4.0" + xml-parser: "npm:^1.2.1" + xpath: "npm:^0.0.27" + peerDependencies: + react-native: "*" + languageName: unknown + linkType: soft +======= + "@babel/core": "npm:^7.25.2" + "@react-native-community/cli-doctor": "npm:20.0.0" + "@react-native-community/cli-types": "npm:20.0.0" + "@react-native-windows/codegen": "npm:0.0.0-canary.133" + "@react-native-windows/find-dotnet-tools": "npm:0.0.0-canary.1" + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@react-native-windows/package-utils": "npm:^0.0.0-canary.98" + "@react-native-windows/telemetry": "npm:^0.0.0-canary.133" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/jest-unittest-config": "npm:1.5.12" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/chalk": "npm:^2.2.0" + "@types/jest": "npm:^29.2.2" + "@types/lodash": "npm:^4.14.168" + "@types/mustache": "npm:^4.1.1" + "@types/node": "npm:^22.14.0" + "@types/ora": "npm:^3.2.0" + "@types/prompts": "npm:2.0.10" + "@types/semver": "npm:^7.3.3" + "@types/shelljs": "npm:^0.8.8" + "@types/xml-parser": "npm:^1.2.29" + "@types/xmldom": "npm:^0.1.30" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + "@xmldom/xmldom": "npm:^0.7.7" + babel-jest: "npm:^29.6.3" + chalk: "npm:^4.1.0" + cli-spinners: "npm:^2.2.0" + envinfo: "npm:^7.5.0" + eslint: "npm:^8.19.0" + execa: "npm:^5.0.0" + find-up: "npm:^4.1.0" + glob: "npm:^7.1.1" + jest: "npm:^29.7.0" + lodash: "npm:^4.17.15" + mustache: "npm:^4.0.1" + ora: "npm:^3.4.0" + prettier: "npm:^3.6.2" + prompts: "npm:^2.4.1" + react: "npm:19.2.3" + react-native: "npm:0.85.0-nightly-20260114-f15985f4f" + semver: "npm:^7.3.2" + shelljs: "npm:^0.8.4" + typescript: "npm:5.0.4" + username: "npm:^5.1.0" + xml-formatter: "npm:^2.4.0" + xml-parser: "npm:^1.2.1" + xpath: "npm:^0.0.27" + peerDependencies: + react-native: "*" + languageName: unknown + linkType: soft +>>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) "@react-native-windows/package-utils@^0.0.0-canary.96": version "0.0.0-canary.98" @@ -2649,10 +2811,69 @@ lodash "^4.17.15" minimatch "^10.0.3" +<<<<<<< HEAD "@react-native/assets-registry@0.84.1": version "0.84.1" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.84.1.tgz#3dbcf131b65305af36bba9df3071862643bcd78d" integrity sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg== +||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) +"@react-native-windows/find-repo-root@npm:^0.0.0-canary.101, @react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root": + version: 0.0.0-use.local + resolution: "@react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root" + dependencies: + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/find-up": "npm:^4.0.0" + "@types/node": "npm:^22.14.0" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + eslint: "npm:^8.19.0" + find-up: "npm:^4.1.0" + minimatch: "npm:^10.0.3" + prettier: "npm:^3.6.2" + typescript: "npm:5.0.4" + languageName: unknown + linkType: soft +======= +"@react-native-windows/find-dotnet-tools@npm:0.0.0-canary.1, @react-native-windows/find-dotnet-tools@workspace:packages/@react-native-windows/find-dotnet-tools": + version: 0.0.0-use.local + resolution: "@react-native-windows/find-dotnet-tools@workspace:packages/@react-native-windows/find-dotnet-tools" + dependencies: + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/node": "npm:^22.14.0" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + eslint: "npm:^8.19.0" + prettier: "npm:^3.6.2" + typescript: "npm:5.0.4" + languageName: unknown + linkType: soft + +"@react-native-windows/find-repo-root@npm:^0.0.0-canary.101, @react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root": + version: 0.0.0-use.local + resolution: "@react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root" + dependencies: + "@react-native-windows/fs": "npm:^0.0.0-canary.72" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/ts-config": "npm:2.0.6" + "@types/find-up": "npm:^4.0.0" + "@types/node": "npm:^22.14.0" + "@typescript-eslint/eslint-plugin": "npm:^8.36.0" + "@typescript-eslint/parser": "npm:^8.36.0" + eslint: "npm:^8.19.0" + find-up: "npm:^4.1.0" + minimatch: "npm:^10.0.3" + prettier: "npm:^3.6.2" + typescript: "npm:5.0.4" + languageName: unknown + linkType: soft +>>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) "@react-native/assets@1.0.0": version "1.0.0" @@ -11105,6 +11326,7 @@ react-native-platform-override@0.0.0-canary.1017: resolved "https://registry.yarnpkg.com/react-native-platform-override/-/react-native-platform-override-0.0.0-canary.1017.tgz#c94c9d8b2435a806a334a191ec6e571345fd0bb0" integrity sha512-9fM3piBVzxa1xtsVwpXaODUNZmxMOVf6kjkygnoKhdRcYTXkRC34lmJWfq18BL25wJdx9ZEZp0dGGGDwD0ujFg== dependencies: +<<<<<<< HEAD "@react-native-windows/fs" "^0.0.0-canary.70" "@react-native-windows/package-utils" "^0.0.0-canary.96" "@typescript-eslint/eslint-plugin" "^7.1.1" @@ -11124,6 +11346,158 @@ react-native-platform-override@0.0.0-canary.1017: source-map-support "^0.5.19" upath "^1.2.0" yargs "^16.2.0" +||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) + "@babel/core": "npm:^7.25.2" + "@babel/preset-env": "npm:^7.25.3" + "@babel/runtime": "npm:^7.0.0" + "@jest/create-cache-key-function": "npm:^29.7.0" + "@react-native-community/cli": "npm:20.0.0" + "@react-native-community/cli-platform-android": "npm:20.0.0" + "@react-native-community/cli-platform-ios": "npm:20.0.0" + "@react-native-windows/cli": "npm:0.0.0-canary.288" + "@react-native-windows/codegen": "npm:0.0.0-canary.133" + "@react-native/assets": "npm:1.0.0" + "@react-native/assets-registry": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/codegen": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/community-cli-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/gradle-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/js-polyfills": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/metro-config": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/new-app-screen": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/normalize-colors": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/virtualized-lists": "npm:0.85.0-nightly-20260114-f15985f4f" + "@rnw-scripts/babel-react-native-config": "npm:0.0.0" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "npm:^1.1.42" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/metro-dev-config": "npm:0.0.0" + "@rnx-kit/jest-preset": "npm:^0.3.1" + "@types/node": "npm:^22.14.0" + "@types/react": "npm:^19.2.3" + abort-controller: "npm:^3.0.0" + anser: "npm:^1.4.9" + ansi-regex: "npm:^5.0.0" + babel-jest: "npm:^29.7.0" + babel-plugin-syntax-hermes-parser: "npm:0.32.0" + base64-js: "npm:^1.5.1" + chalk: "npm:^4.0.0" + commander: "npm:^12.0.0" + eslint: "npm:^8.19.0" + event-target-shim: "npm:^5.0.1" + flow-bin: "npm:^0.296.1" + flow-enums-runtime: "npm:^0.0.6" + glob: "npm:^7.1.1" + hermes-compiler: "npm:0.14.0-commitly-202512102158-39fca9fda" + invariant: "npm:^2.2.4" + jest-environment-node: "npm:^29.7.0" + jscodeshift: "npm:^0.14.0" + just-scripts: "npm:^1.3.3" + memoize-one: "npm:^5.0.0" + metro-runtime: "npm:^0.83.3" + metro-source-map: "npm:^0.83.3" + mkdirp: "npm:^0.5.1" + nullthrows: "npm:^1.1.1" + prettier: "npm:^3.6.2" + pretty-format: "npm:^29.7.0" + promise: "npm:^8.3.0" + react: "npm:19.2.3" + react-devtools-core: "npm:^6.1.5" + react-native: "npm:0.85.0-nightly-20260114-f15985f4f" + react-native-platform-override: "npm:0.0.0-canary.1022" + react-refresh: "npm:^0.14.0" + regenerator-runtime: "npm:^0.13.2" + scheduler: "npm:0.27.0" + semver: "npm:^7.1.3" + source-map-support: "npm:^0.5.19" + stacktrace-parser: "npm:^0.1.10" + tinyglobby: "npm:^0.2.15" + typescript: "npm:5.0.4" + whatwg-fetch: "npm:^3.0.0" + ws: "npm:^7.5.10" + yargs: "npm:^17.6.2" + peerDependencies: + "@types/react": ^19.2.3 + react: ^19.2.3 + react-native: 0.85.0-nightly-20260114-f15985f4f + languageName: unknown + linkType: soft +======= + "@babel/core": "npm:^7.25.2" + "@babel/preset-env": "npm:^7.25.3" + "@babel/runtime": "npm:^7.0.0" + "@jest/create-cache-key-function": "npm:^29.7.0" + "@react-native-community/cli": "npm:20.0.0" + "@react-native-community/cli-platform-android": "npm:20.0.0" + "@react-native-community/cli-platform-ios": "npm:20.0.0" + "@react-native-windows/cli": "npm:0.0.0-canary.288" + "@react-native-windows/codegen": "npm:0.0.0-canary.133" + "@react-native-windows/find-dotnet-tools": "npm:0.0.0-canary.1" + "@react-native/assets": "npm:1.0.0" + "@react-native/assets-registry": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/codegen": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/community-cli-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/gradle-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/js-polyfills": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/metro-config": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/new-app-screen": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/normalize-colors": "npm:0.85.0-nightly-20260114-f15985f4f" + "@react-native/virtualized-lists": "npm:0.85.0-nightly-20260114-f15985f4f" + "@rnw-scripts/babel-react-native-config": "npm:0.0.0" + "@rnw-scripts/eslint-config": "npm:1.2.38" + "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "npm:^1.1.42" + "@rnw-scripts/just-task": "npm:2.3.58" + "@rnw-scripts/metro-dev-config": "npm:0.0.0" + "@rnx-kit/jest-preset": "npm:^0.3.1" + "@types/node": "npm:^22.14.0" + "@types/react": "npm:^19.2.3" + abort-controller: "npm:^3.0.0" + anser: "npm:^1.4.9" + ansi-regex: "npm:^5.0.0" + babel-jest: "npm:^29.7.0" + babel-plugin-syntax-hermes-parser: "npm:0.32.0" + base64-js: "npm:^1.5.1" + chalk: "npm:^4.0.0" + commander: "npm:^12.0.0" + eslint: "npm:^8.19.0" + event-target-shim: "npm:^5.0.1" + flow-bin: "npm:^0.296.1" + flow-enums-runtime: "npm:^0.0.6" + glob: "npm:^7.1.1" + hermes-compiler: "npm:0.14.0-commitly-202512102158-39fca9fda" + invariant: "npm:^2.2.4" + jest-environment-node: "npm:^29.7.0" + jscodeshift: "npm:^0.14.0" + just-scripts: "npm:^1.3.3" + memoize-one: "npm:^5.0.0" + metro-runtime: "npm:^0.83.3" + metro-source-map: "npm:^0.83.3" + mkdirp: "npm:^0.5.1" + nullthrows: "npm:^1.1.1" + prettier: "npm:^3.6.2" + pretty-format: "npm:^29.7.0" + promise: "npm:^8.3.0" + react: "npm:19.2.3" + react-devtools-core: "npm:^6.1.5" + react-native: "npm:0.85.0-nightly-20260114-f15985f4f" + react-native-platform-override: "npm:0.0.0-canary.1022" + react-refresh: "npm:^0.14.0" + regenerator-runtime: "npm:^0.13.2" + scheduler: "npm:0.27.0" + semver: "npm:^7.1.3" + source-map-support: "npm:^0.5.19" + stacktrace-parser: "npm:^0.1.10" + tinyglobby: "npm:^0.2.15" + typescript: "npm:5.0.4" + whatwg-fetch: "npm:^3.0.0" + ws: "npm:^7.5.10" + yargs: "npm:^17.6.2" + peerDependencies: + "@types/react": ^19.2.3 + react: ^19.2.3 + react-native: 0.85.0-nightly-20260114-f15985f4f + languageName: unknown + linkType: soft +>>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) react-native@0.84.1: version "0.84.1" From 1a5f67f669e60a324c986b132c84429e6e405f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Wed, 20 May 2026 14:16:25 -0700 Subject: [PATCH 02/23] Fix PowerShell installation (#16164) * Defer findPowershell to layoutMSRNCxx * Change files --- ...ative-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json | 7 +++++++ vnext/just-task.js | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json diff --git a/change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json b/change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json new file mode 100644 index 00000000000..9cb98e354c8 --- /dev/null +++ b/change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Defer findPowershell to layoutMSRNCxx", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/just-task.js b/vnext/just-task.js index e10779044fb..ef22fe5fbbd 100644 --- a/vnext/just-task.js +++ b/vnext/just-task.js @@ -45,10 +45,9 @@ function codegen(test) { ); } -const powershell = findPowerShell(); - function layoutMSRNCxx() { if (require('os').platform() === 'win32') { + const powershell = findPowerShell(); execSync( `"${powershell}" -NoProfile .\\Scripts\\Tfs\\Layout-MSRN-Headers.ps1 -GenerateLocalCxx`, { From 648c3935d2210a5a210581688af944cb42686800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Wed, 27 May 2026 19:45:21 -0700 Subject: [PATCH 03/23] Upgrade to Visual Studio 2026 (#16170) * Update deps script. TODO: test * Upgrade rnw-deps to .NET 10 * Defer findPowershell to layoutMSRNCxx * Upgrade VS version to 18.6.1 * Drop /async in MSVC 14.5 * Replace std::future coroutines with WinRT types * clang format * Change files * Drop Windows10SDK.19041 * Change files * Set TargetFramework to net10.0-windows10.0.26100.0 * Use IAsyncOperation instead of out parameters * Install .NET 10 * Revert "Install .NET 10" This reverts commit 5f75dc0cb5edf06607eb97e3d4089857740512b1. * Revert "Set TargetFramework to net10.0-windows10.0.26100.0" This reverts commit 22a4791e8494b6fcf89ba077376c044eedc2659d. * clang format * Upgrade CsWinRT.csproj to .NET 8 * Fix filters * Upgrade MSRN SLN to VS 18 * Revert parameter signature in LoadBundleAsync * Update CSWinRT lock * Allow all versions of VS 2026 * Use IBuffer instead of hstring as payload GetJavaScriptFromServerAsync retuns bundle contents as hstring (16 bit) It then gets converted to an 8-bit string. This conversion drops bytes consideres "invalid". Those bytes are expected as part of the Hermes bundle header. Transmitting the bundle as an IBuffer preserves the original bytes and avoids the conversion issues. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Use WinRT exception types instead of std --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ...-543cf36f-0ce7-4df7-a391-285985d3f62c.json | 7 ++ ...-2a20c629-48b2-458d-80e6-26b5546c04e0.json | 7 ++ .../commands/healthCheck/healthCheckList.ts | 4 +- .../cli/src/utils/vsInstalls.ts | 4 +- .../React.Windows.Desktop.UnitTests.vcxproj | 2 +- .../Microsoft.ReactNative.CsWinRT.csproj | 2 +- .../packages.experimentalwinui3.lock.json | 10 +- .../packages.lock.json | 10 +- ...icrosoft.ReactNative.Cxx.UnitTests.vcxproj | 3 +- ...osoft.ReactNative.IntegrationTests.vcxproj | 3 +- vnext/Microsoft.ReactNative.NewArch.sln | 11 +- .../Microsoft.ReactNative.vcxproj | 3 +- .../Utils/LocalBundleReader.cpp | 58 +++++----- .../Utils/LocalBundleReader.h | 8 +- vnext/Mso.UnitTests/Mso.UnitTests.vcxproj | 3 +- vnext/PropertySheets/React.Cpp.props | 3 +- vnext/Scripts/rnw-dependencies.ps1 | 31 +++--- vnext/Shared/DevSupportManager.cpp | 103 ++++++++++-------- vnext/Shared/DevSupportManager.h | 1 - .../Networking/WinRTWebSocketResource.h | 6 +- 20 files changed, 163 insertions(+), 116 deletions(-) create mode 100644 change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json create mode 100644 change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json diff --git a/change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json b/change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json new file mode 100644 index 00000000000..0552570c866 --- /dev/null +++ b/change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to Visual Studio 2026", + "packageName": "@react-native-windows/cli", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json b/change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json new file mode 100644 index 00000000000..74bc56dcc93 --- /dev/null +++ b/change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to Visual Studio 2026", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts b/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts index 6e34f37cfe2..c4451f5019b 100644 --- a/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts +++ b/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts @@ -11,8 +11,8 @@ export const HealthCheckList = [ [true, 'WindowsVersion', 'Windows version >= 10.0.17763.0'], [true, 'DeveloperMode', 'Developer mode is on'], [true, 'LongPath', 'Long path support is enabled'], - [true, 'VSUWP', 'Visual Studio 2022 (>= 17.11.0) & req. components'], + [true, 'VSUWP', 'Visual Studio 2026 (>= 18.6.1) & req. components'], [true, 'Node', 'Node.js (LTS, >= 22.0)'], [true, 'Yarn', 'Yarn'], - [true, 'DotNetCore', '.NET SDK (LTS, = 8.0)'], + [true, 'DotNetCore', '.NET SDK (LTS, = 10.0)'], ]; diff --git a/packages/@react-native-windows/cli/src/utils/vsInstalls.ts b/packages/@react-native-windows/cli/src/utils/vsInstalls.ts index 856cb052f7e..1d529177d89 100644 --- a/packages/@react-native-windows/cli/src/utils/vsInstalls.ts +++ b/packages/@react-native-windows/cli/src/utils/vsInstalls.ts @@ -82,12 +82,12 @@ export function enumerateVsInstalls(opts: { if (minVersionSemVer) { minVersion = minVersionSemVer.toString(); - maxVersion = `${minVersionSemVer.major + 1}.0`; + maxVersion = `${minVersionSemVer.major + 2}.0`; } else if (!Number.isNaN(minVersionNum)) { minVersion = Number.isInteger(minVersionNum) ? `${minVersionNum}.0` : minVersionNum.toString(); - maxVersion = `${Math.floor(minVersionNum) + 1}.0`; + maxVersion = `${Math.floor(minVersionNum) + 2}.0`; } else { // Unable to parse minVersion and determine maxVersion, // caller will throw error that version couldn't be found. diff --git a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj index 83eb2840d0a..2ac2d6d4c58 100644 --- a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj +++ b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj @@ -79,7 +79,7 @@ $(VCInstallDir)UnitTest\include; %(AdditionalIncludeDirectories) - %(AdditionalOptions) /await + %(AdditionalOptions) /await - /await %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /await true Cdecl diff --git a/vnext/Microsoft.ReactNative.NewArch.sln b/vnext/Microsoft.ReactNative.NewArch.sln index 46c8480284b..70f9c21dddb 100644 --- a/vnext/Microsoft.ReactNative.NewArch.sln +++ b/vnext/Microsoft.ReactNative.NewArch.sln @@ -1,12 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32929.385 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11819.183 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}" - ProjectSection(ProjectDependencies) = postProject - {14B93DC8-FD93-4A6D-81CB-8BC96644501C} = {14B93DC8-FD93-4A6D-81CB-8BC96644501C} - EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}" EndProject @@ -111,11 +108,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ReactNative.CsWin EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 + Debug|ARM64 = Debug|ARM64 Debug|x86 = Debug|x86 - Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 + Release|ARM64 = Release|ARM64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index c44789aaba5..de6be8e4203 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -96,7 +96,8 @@ $(IntDir)pch.pch pch.h Level4 - /await %(AdditionalOptions) /bigobj /ZH:SHA_256 + %(AdditionalOptions) /bigobj /ZH:SHA_256 + %(AdditionalOptions) /await $(FmtDir)\include; $(ReactNativeWindowsDir)Microsoft.ReactNative; diff --git a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp index 5194f68f6de..f203757a28e 100644 --- a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp +++ b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp @@ -54,7 +54,29 @@ std::string GetBundleFromEmbeddedResource(const winrt::Windows::Foundation::Uri return std::string(start, start + size); } -std::future LocalBundleReader::LoadBundleAsync(const std::wstring bundleUri) { +namespace { + +std::string BufferToString(const winrt::Windows::Storage::Streams::IBuffer &buffer) { + std::string result(buffer.Length(), '\0'); + if (!result.empty()) { + auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); + reader.ReadBytes(winrt::array_view{ + reinterpret_cast(&result[0]), reinterpret_cast(&result[result.length()])}); + } + return result; +} + +winrt::Windows::Storage::Streams::IBuffer BytesToBuffer(const void *data, uint32_t size) { + winrt::Windows::Storage::Streams::DataWriter writer; + auto bytes = static_cast(data); + writer.WriteBytes(winrt::array_view(bytes, bytes + size)); + return writer.DetachBuffer(); +} + +} // namespace + +winrt::Windows::Foundation::IAsyncOperation +LocalBundleReader::LoadBundleAsync(const std::wstring bundleUri) { try { co_await winrt::resume_background(); @@ -66,41 +88,26 @@ std::future LocalBundleReader::LoadBundleAsync(const std::wstring b file = co_await winrt::Windows::Storage::StorageFile::GetFileFromApplicationUriAsync(uri); } else if (bundleUri.starts_with(L"resource://")) { winrt::Windows::Foundation::Uri uri(bundleUri); - co_return GetBundleFromEmbeddedResource(uri); + auto bytes = GetBundleFromEmbeddedResource(uri); + co_return BytesToBuffer(bytes.data(), static_cast(bytes.size())); } else { file = co_await winrt::Windows::Storage::StorageFile::GetFileFromPathAsync(bundleUri); } - // Read the buffer manually to avoid a Utf8 -> Utf16 -> Utf8 encoding - // roundtrip. - auto fileBuffer{co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(file)}; - auto dataReader{winrt::Windows::Storage::Streams::DataReader::FromBuffer(fileBuffer)}; - - // No need to use length + 1, STL guarantees that string storage is null-terminated. - std::string script(fileBuffer.Length(), '\0'); - - // Construct the array_view to slice into the first fileBuffer.Length bytes. - // DataReader.ReadBytes will read as many bytes as are present in the - // array_view. The backing string has fileBuffer.Length() + 1 bytes, without - // an explicit end it will read 1 byte to many and throw. - dataReader.ReadBytes(winrt::array_view{ - reinterpret_cast(&script[0]), reinterpret_cast(&script[script.length()])}); - dataReader.Close(); - - co_return script; + co_return co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(file); } // RuntimeScheduler only handles std::exception or jsi::JSError - catch (winrt::hresult_error const &e) { - throw std::exception(winrt::to_string(e.message()).c_str()); + catch (winrt::hresult_error const &) { + throw; } } std::string LocalBundleReader::LoadBundle(const std::wstring &bundlePath) { - return LoadBundleAsync(bundlePath).get(); + return BufferToString(LoadBundleAsync(bundlePath).get()); } StorageFileBigString::StorageFileBigString(const std::wstring &path) { - m_futureBuffer = LocalBundleReader::LoadBundleAsync(path); + m_pendingLoad = LocalBundleReader::LoadBundleAsync(path); } bool StorageFileBigString::isAscii() const { @@ -118,8 +125,9 @@ size_t StorageFileBigString::size() const { } void StorageFileBigString::ensure() const { - if (m_string.empty()) { - m_string = m_futureBuffer.get(); + if (m_pendingLoad) { + m_string = BufferToString(m_pendingLoad.get()); + m_pendingLoad = nullptr; } } diff --git a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h index 8671ebf59ee..4286189b94d 100644 --- a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h +++ b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h @@ -3,14 +3,16 @@ #pragma once #include -#include +#include +#include #include namespace Microsoft::ReactNative { class LocalBundleReader { public: - static std::future LoadBundleAsync(const std::wstring bundlePath); + static winrt::Windows::Foundation::IAsyncOperation LoadBundleAsync( + const std::wstring bundlePath); static std::string LoadBundle(const std::wstring &bundlePath); }; @@ -24,7 +26,7 @@ class StorageFileBigString : public facebook::react::JSBigString { void ensure() const; private: - mutable std::future m_futureBuffer; + mutable winrt::Windows::Foundation::IAsyncOperation m_pendingLoad; mutable std::string m_string; }; diff --git a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj index 74fb6cc93fa..1609c75d257 100644 --- a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj +++ b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj @@ -94,7 +94,8 @@ /bigobj - /FS - Force Synchronous PDB writes. Useful when setting MultiProcCL. --> - /await %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /await true Cdecl diff --git a/vnext/PropertySheets/React.Cpp.props b/vnext/PropertySheets/React.Cpp.props index 0c757c4b5a2..0b5ce119add 100644 --- a/vnext/PropertySheets/React.Cpp.props +++ b/vnext/PropertySheets/React.Cpp.props @@ -136,7 +136,8 @@ ProgramDatabase true true - /utf-8 %(AdditionalOptions) /await + /utf-8 %(AdditionalOptions) + %(AdditionalOptions) /await Guard Spectre diff --git a/vnext/Scripts/rnw-dependencies.ps1 b/vnext/Scripts/rnw-dependencies.ps1 index a57d7809f5c..6ec18cf8140 100644 --- a/vnext/Scripts/rnw-dependencies.ps1 +++ b/vnext/Scripts/rnw-dependencies.ps1 @@ -8,7 +8,7 @@ param( [string]$Check = [CheckId]::All, [Parameter(ValueFromRemainingArguments)] - [ValidateSet('appDev', 'rnwDev', 'buildLab', 'vs2022', 'clone')] + [ValidateSet('appDev', 'rnwDev', 'buildLab', 'vs2026', 'clone')] [String[]]$Tags = @('appDev'), [switch]$Enterprise = $false ) @@ -94,7 +94,6 @@ $vsComponents = @('Microsoft.Component.MSBuild', $vcToolsComponent, 'Microsoft.VisualStudio.ComponentGroup.UWP.Support', 'Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core', - 'Microsoft.VisualStudio.Component.Windows10SDK.19041', 'Microsoft.VisualStudio.Component.Windows11SDK.22621'); # UWP.VC is not needed to build the projects with msbuild, but the VS IDE requires it. @@ -113,12 +112,12 @@ $wingetver = "1.7.11261"; # The minimum VS version to check for # Note: For install to work, whatever min version you specify here must be met by the current package available on winget. -$vsver = "17.11.0"; +$vsver = "18.6.1"; # The exact .NET SDK version to check for -$dotnetver = "8.0"; +$dotnetver = "10.0"; # Version name of the winget package -$wingetDotNetVer = "8"; +$wingetDotNetVer = "10"; $v = [System.Environment]::OSVersion.Version; if ($env:Agent_BuildDirectory) { @@ -242,9 +241,9 @@ function InstallVS { if ($Enterprise) { # The CI machines need the enterprise version of VS as that is what is hardcoded in all the scripts - WinGetInstall Microsoft.VisualStudio.2022.Enterprise + WinGetInstall Microsoft.VisualStudio.Enterprise } else { - WinGetInstall Microsoft.VisualStudio.2022.Community + WinGetInstall Microsoft.VisualStudio.Community } $vsWhere = Get-VSWhere; @@ -458,8 +457,8 @@ $requirements = @( }, @{ Id=[CheckId]::VSUWP; - Name = "Visual Studio 2022 (>= $vsver) & req. components"; - Tags = @('appDev', 'vs2022'); + Name = "Visual Studio 2026 (>= $vsver) & req. components"; + Tags = @('appDev', 'vs2026'); Valid = { CheckVS; } Install = { InstallVS }; HasVerboseOutput = $true; @@ -491,7 +490,7 @@ $requirements = @( $downloadPath = "$env:TEMP\WindowsApplicationDriver.msi" Write-Verbose "Downloading WinAppDriver from $url"; Invoke-WebRequest -UseBasicParsing $url -OutFile $downloadPath - + # SDL Compliance: Verify signature (Work Item 58386093) $signature = Get-AuthenticodeSignature $downloadPath if ($signature.Status -ne "Valid") { @@ -499,10 +498,10 @@ $requirements = @( throw "WinAppDriver signature verification failed" } if ($signature.SignerCertificate.Subject -notlike "*Microsoft*") { - Remove-Item $downloadPath -ErrorAction SilentlyContinue + Remove-Item $downloadPath -ErrorAction SilentlyContinue throw "WinAppDriver not signed by Microsoft" } - + & $downloadPath /q Remove-Item $downloadPath -ErrorAction SilentlyContinue }; @@ -600,7 +599,7 @@ function WinGetInstall { Write-Verbose "Executing `winget install `"$wingetPackage`""; & winget install "$wingetPackage" --accept-source-agreements --accept-package-agreements } - + # Refresh PATH environment variable to pick up newly installed tools $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") } @@ -693,12 +692,12 @@ foreach ($req in $filteredRequirements) try { $validAfterInstall = Invoke-Command $req.Valid; } catch { } - + if ($validAfterInstall) { $Installed++; continue; # go to the next item } - + if ($LASTEXITCODE -ne 0) { throw "Last exit code was non-zero: $LASTEXITCODE - $outputFromInstall"; } @@ -737,4 +736,4 @@ if ($NeedsRerun -ne 0) { $Tags | Out-File $MarkerFile; if (!$ShellInvocation) { Read-Host 'Press Enter to exit' } exit 0; -} \ No newline at end of file +} diff --git a/vnext/Shared/DevSupportManager.cpp b/vnext/Shared/DevSupportManager.cpp index b485fc1b304..b964abcdce2 100644 --- a/vnext/Shared/DevSupportManager.cpp +++ b/vnext/Shared/DevSupportManager.cpp @@ -33,7 +33,6 @@ #include #pragma warning(pop) -#include #include #include @@ -47,61 +46,59 @@ using namespace facebook::react; namespace Microsoft::ReactNative { -std::future> GetJavaScriptFromServerAsync(const std::string &url) { - winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter; - filter.CacheControl().ReadBehavior(winrt::Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache); - winrt::Windows::Web::Http::HttpClient httpClient(filter); - winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); +winrt::Windows::Foundation::IAsyncOperation GetJavaScriptFromServerAsync( + const std::string &url) { + try { + winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter; + filter.CacheControl().ReadBehavior(winrt::Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache); + winrt::Windows::Web::Http::HttpClient httpClient(filter); + winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); - co_await winrt::resume_background(); + co_await winrt::resume_background(); - winrt::Windows::Web::Http::HttpRequestMessage request(winrt::Windows::Web::Http::HttpMethod::Get(), uri); - auto asyncRequest = httpClient.SendRequestAsync(request); + winrt::Windows::Web::Http::HttpRequestMessage request(winrt::Windows::Web::Http::HttpMethod::Get(), uri); + auto asyncRequest = httpClient.SendRequestAsync(request); #ifdef DEFAULT_CPPWINRT_EXCEPTIONS - try { winrt::Windows::Web::Http::HttpResponseMessage response = co_await asyncRequest; - } catch (winrt::hresult_error const &e) { - co_return std::make_pair( - Microsoft::Common::Unicode::Utf16ToUtf8(e.message().c_str(), e.message().size()).c_str(), false); - } #else - co_await lessthrow_await_adapter>{asyncRequest}; - - HRESULT hr = asyncRequest.ErrorCode(); - if (FAILED(hr)) { - std::string error; - if (hr == WININET_E_CANNOT_CONNECT) { - error = fmt::format("A connection with the server {} could not be established.\n\nIs the packager running?", url); - } else { - error = fmt::format("Error 0x{:x} downloading {}.", static_cast(asyncRequest.ErrorCode()), url); + co_await lessthrow_await_adapter>{asyncRequest}; + + HRESULT hr = asyncRequest.ErrorCode(); + if (FAILED(hr)) { + std::string error; + if (hr == WININET_E_CANNOT_CONNECT) { + error = + fmt::format("A connection with the server {} could not be established.\n\nIs the packager running?", url); + } else { + error = fmt::format("Error 0x{:x} downloading {}.", static_cast(asyncRequest.ErrorCode()), url); + } + throw winrt::hresult_error(E_FAIL, winrt::to_hstring(error)); } - co_return std::make_pair(error, false); - } - winrt::Windows::Web::Http::HttpResponseMessage response = asyncRequest.GetResults(); + winrt::Windows::Web::Http::HttpResponseMessage response = asyncRequest.GetResults(); #endif - winrt::Windows::Storage::Streams::IBuffer buffer = co_await response.Content().ReadAsBufferAsync(); - auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); - - reader.UnicodeEncoding(winrt::Windows::Storage::Streams::UnicodeEncoding::Utf8); - uint32_t len = reader.UnconsumedBufferLength(); - std::string result; - if (len > 0 || response.IsSuccessStatusCode()) { - std::string data; - data.resize(len); - auto buf = reinterpret_cast(data.data()); - static_assert( - sizeof(buf[0]) == sizeof(data[0]), "perf optimization relies on uint8_t and char being the same size"); - reader.ReadBytes(winrt::array_view(buf, buf + len)); - result = std::move(data); - } else { - result = fmt::format("HTTP Error {} downloading {}.", static_cast(response.StatusCode()), url); - } + winrt::Windows::Storage::Streams::IBuffer buffer = co_await response.Content().ReadAsBufferAsync(); + + if (!response.IsSuccessStatusCode()) { + std::string error; + if (buffer.Length() > 0) { + auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); + error.resize(buffer.Length()); + auto buf = reinterpret_cast(error.data()); + reader.ReadBytes(winrt::array_view(buf, buf + buffer.Length())); + } else { + error = fmt::format("HTTP Error {} downloading {}.", static_cast(response.StatusCode()), url); + } + throw winrt::hresult_error(E_FAIL, winrt::to_hstring(error)); + } - co_return std::make_pair(std::move(result), response.IsSuccessStatusCode()); + co_return buffer; + } catch (winrt::hresult_error const &) { + throw; + } } void LaunchDevTools(const facebook::react::DevSettings &settings) { @@ -185,7 +182,8 @@ std::string GetPackageName(const std::string &bundleAppId) { return packageName; } -std::future PollForLiveReload(const std::string &url) { +winrt::Windows::Foundation::IAsyncOperation PollForLiveReload( + const std::string &url) { winrt::Windows::Web::Http::HttpClient httpClient; winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); httpClient.DefaultRequestHeaders().Connection().TryParseAdd(L"keep-alive"); @@ -299,7 +297,16 @@ std::pair GetJavaScriptFromServer( inlineSourceMap, hermesBytecodeVersion); try { - return GetJavaScriptFromServerAsync(bundleUrl).get(); + auto buffer = GetJavaScriptFromServerAsync(bundleUrl).get(); + std::string result(buffer.Length(), '\0'); + if (!result.empty()) { + auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); + reader.ReadBytes(winrt::array_view{ + reinterpret_cast(&result[0]), reinterpret_cast(&result[result.length()])}); + } + return std::make_pair(std::move(result), true); + } catch (std::exception const &e) { + return std::make_pair(std::string{"Error: "} + e.what(), false); } catch (winrt::hresult_error const &e) { return std::make_pair( "Error: " + Microsoft::Common::Unicode::Utf16ToUtf8(e.message().c_str(), e.message().size()), false); diff --git a/vnext/Shared/DevSupportManager.h b/vnext/Shared/DevSupportManager.h index 216bd6c4932..849ec08203b 100644 --- a/vnext/Shared/DevSupportManager.h +++ b/vnext/Shared/DevSupportManager.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/vnext/Shared/Networking/WinRTWebSocketResource.h b/vnext/Shared/Networking/WinRTWebSocketResource.h index 8ebdd3b14d0..eafb1c6fbe9 100644 --- a/vnext/Shared/Networking/WinRTWebSocketResource.h +++ b/vnext/Shared/Networking/WinRTWebSocketResource.h @@ -27,8 +27,12 @@ class WinRTWebSocketResource2 : public IWebSocketResource, void operator=(const TaskSequencer &) = delete; private: +// `experimental` is deprecated starting Visual Studio 2026 +#if _MSC_VER >= 1951 + using CoroHandle = std::coroutine_handle<>; +#else using CoroHandle = std::experimental::coroutine_handle<>; - +#endif struct Suspender { CoroHandle m_handle; From 8248e54d1ed39f7f40736d165d311e856365105a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Fri, 5 Jun 2026 12:46:42 -0700 Subject: [PATCH 04/23] Upgrade to GoogleTest Adapter 1.8.1.8 (#16218) * Upgrade to GoogleTest adapter 1.8.1.8 * Change files * Update package lock files --- ...ative-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json | 7 +++++++ .../Microsoft.ReactNative.Cxx.UnitTests.vcxproj | 2 +- .../packages.experimentalwinui3.lock.json | 6 +++--- .../Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json | 6 +++--- .../Microsoft.ReactNative.IntegrationTests.vcxproj | 2 +- .../packages.experimentalwinui3.lock.json | 6 +++--- .../packages.lock.json | 6 +++--- vnext/Mso.UnitTests/Mso.UnitTests.vcxproj | 2 +- vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json | 6 +++--- vnext/Mso.UnitTests/packages.lock.json | 6 +++--- vnext/PropertySheets/React.Cpp.props | 4 ++-- vnext/ReactCommon.UnitTests/ReactCommon.UnitTests.vcxproj | 2 +- .../packages.experimentalwinui3.lock.json | 6 +++--- vnext/ReactCommon.UnitTests/packages.lock.json | 6 +++--- 14 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json diff --git a/change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json b/change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json new file mode 100644 index 00000000000..ee31db063aa --- /dev/null +++ b/change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to GoogleTest adapter 1.8.1.8", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj index 63f176b1173..455cb5aca32 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj @@ -162,7 +162,7 @@ - + diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json index 37e573dcf94..abb0152cdb0 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json index 3077a54470f..236c8f35428 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj index 81b39fcd7e9..405d35af50f 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj +++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj @@ -190,7 +190,7 @@ + Version="1.8.1.8" /> diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json b/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json index f824566167f..29737786ece 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json +++ b/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json b/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json index 655e5224376..744aed7495a 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json +++ b/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", diff --git a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj index 1609c75d257..6d90cf64452 100644 --- a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj +++ b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj @@ -183,7 +183,7 @@ + Version="1.8.1.8" /> diff --git a/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json b/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json index 37e573dcf94..abb0152cdb0 100644 --- a/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json +++ b/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/Mso.UnitTests/packages.lock.json b/vnext/Mso.UnitTests/packages.lock.json index 3077a54470f..236c8f35428 100644 --- a/vnext/Mso.UnitTests/packages.lock.json +++ b/vnext/Mso.UnitTests/packages.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/PropertySheets/React.Cpp.props b/vnext/PropertySheets/React.Cpp.props index 0b5ce119add..cbddacfb5f0 100644 --- a/vnext/PropertySheets/React.Cpp.props +++ b/vnext/PropertySheets/React.Cpp.props @@ -43,8 +43,8 @@ - - false + + true $(WinUI3ExperimentalVersion) - 1.8.260209005 + 1.8.260508005 false diff --git a/vnext/ReactCommon.UnitTests/packages.lock.json b/vnext/ReactCommon.UnitTests/packages.lock.json index 1d38347e136..4d4d60c6f61 100644 --- a/vnext/ReactCommon.UnitTests/packages.lock.json +++ b/vnext/ReactCommon.UnitTests/packages.lock.json @@ -61,27 +61,27 @@ }, "Microsoft.WindowsAppSDK": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "AGHOiZcrDrpaxpHfEFKlI8MVnibfbSixI5DlbU6ozP/9dyWN5FkTFowg+dEOnaFRCnOzTSAjBQ1HuS4lAO+aMQ==", + "resolved": "1.8.260508005", + "contentHash": "+aA+zrvqJKgsn/1TPOSR0Uy7dkMO5jI/+cDWPu2pWmXe4KQxYkR8gQFY9IrfbgCxxSd/yl1zMcAKD/4HBbNqaw==", "dependencies": { - "Microsoft.WindowsAppSDK.AI": "[1.8.47]", + "Microsoft.WindowsAppSDK.AI": "[1.8.76]", "Microsoft.WindowsAppSDK.Base": "[1.8.251216001]", "Microsoft.WindowsAppSDK.DWrite": "[1.8.25122902]", - "Microsoft.WindowsAppSDK.Foundation": "[1.8.260203002]", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260125001]", - "Microsoft.WindowsAppSDK.ML": "[1.8.2124]", - "Microsoft.WindowsAppSDK.Runtime": "[1.8.260209005]", + "Microsoft.WindowsAppSDK.Foundation": "[1.8.260505001]", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260430001]", + "Microsoft.WindowsAppSDK.ML": "[1.8.2197]", + "Microsoft.WindowsAppSDK.Runtime": "[1.8.260508005]", "Microsoft.WindowsAppSDK.Widgets": "[1.8.251231004]", - "Microsoft.WindowsAppSDK.WinUI": "[1.8.260204000]" + "Microsoft.WindowsAppSDK.WinUI": "[1.8.260505002]" } }, "Microsoft.WindowsAppSDK.AI": { "type": "Transitive", - "resolved": "1.8.47", - "contentHash": "9il8KT8WR4T826hnm3M/USZTkPtVXFGE0IztmE1l7H9DPYsa3QHEUgGHFHQg88fsMjdr3vhyMvs23AB+1IYF1w==", + "resolved": "1.8.76", + "contentHash": "Ayn9QybcwzH+c8eQlE7dm2oO3Jrcn2uohLcsHJpCbLOAfcisjzwtSBe0oyulbaJ86R4eDSX3RDS25tsjGpIqyQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260501000" } }, "Microsoft.WindowsAppSDK.Base": { @@ -103,34 +103,34 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "Microsoft.WindowsAppSDK.InteractiveExperiences": { "type": "Transitive", - "resolved": "1.8.260125001", - "contentHash": "CTGFd1zhIDbnOltZ6piPvpNXFR1OaNyW3vHvhaILzpGziAgj5DPuVnU3PUp1p5iOBd382FLCBVM6nEPyu/LCOA==", + "resolved": "1.8.260430001", + "contentHash": "fTPCnQb3ZarMh9khlEfbLDllcZzK0tSP+2S6W/T4cPyLLSAmARm8Gd3AC5kqubnDjzTvG+1lNZ1bZGFsIHplJQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } }, "Microsoft.WindowsAppSDK.ML": { "type": "Transitive", - "resolved": "1.8.2124", - "contentHash": "l7ZptLbvOWHEJgxZtCQhUzDNCakNcqSJyAa7DNXBLKxGIUMDqq9LnWyYRZZFNQwN7hRfDAR8fEAblP1UHYHGgw==", + "resolved": "1.8.2197", + "contentHash": "6Bc1SOLd5HicY3GbF+zr76YBfH4iZOKeEGxTK/lHKAK2ExZTWavOADxB2CKSi/irF4dWSngUdRFopWPmckJ6fA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001" } }, "Microsoft.WindowsAppSDK.Runtime": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "aZjMu/glUGjzACowzzhj9drn/Ddfp1yA+f7CFXpkiSk6iZ2x32vhKfcqT64RpJ6R+Dj1hl9/79aXFhIavYNj9g==", + "resolved": "1.8.260508005", + "contentHash": "2JqXzA4heHSkkaXJNyEvYtpv2wsEUMhwQzgXmZsFmYSkUXSdeVc6hdHcWoBK1SY9l1Sjd1brLt7h16kIpgh2kA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } @@ -145,13 +145,13 @@ }, "Microsoft.WindowsAppSDK.WinUI": { "type": "Transitive", - "resolved": "1.8.260204000", - "contentHash": "DSpA01+iPXwky4O1uZCrdClSi2aRIYTIhmsTeC1EsJmWBFpSirwNAg4EGHejijV6u4ZVkTdyv3px0Y2P3fp72Q==", + "resolved": "1.8.260505002", + "contentHash": "/hGl6EOmo8aeJR2bYbOmGhOicnJK4EY+u+cQI+jm8H1uXThfMX32DsfKOt9LkOcu8AKX3j5FpiBay8ObUSytIw==", "dependencies": { "Microsoft.Web.WebView2": "1.0.3179.45", "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260203002", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "common": { @@ -184,7 +184,7 @@ "FollyWin32": "[1.0.0, )", "Microsoft.JavaScript.Hermes": "[0.0.0-2605.6002-2279da22, )", "Microsoft.SourceLink.GitHub": "[1.1.1, )", - "Microsoft.WindowsAppSDK": "[1.8.260209005, )", + "Microsoft.WindowsAppSDK": "[1.8.260508005, )", "ReactCommon": "[1.0.0, )", "ReactNative.V8Jsi.Windows": "[0.71.8, )", "boost": "[1.84.0, )" @@ -206,11 +206,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -222,11 +222,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -238,11 +238,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -254,11 +254,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } } From 13fed7c9b6d9dbe233f45abdcf6e95f0671a4b15 Mon Sep 17 00:00:00 2001 From: Gordon MacMaster <31481849+gmacmaster@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:16:10 -0400 Subject: [PATCH 09/23] =?UTF-8?q?fix:=20WebSocket=20binaryType=20handling?= =?UTF-8?q?=20=E2=80=94=20stop=20unconditional=20Blob=20interception=20of?= =?UTF-8?q?=20binary=20messages=20(#16173)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * blob support (#8) * blob support * pr comments * pr comments * Update DefaultBlobResource.cpp * Create react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json * Add WebSocketArrayBuffer headless test (#9) * Add WebSocketArrayBuffer headless test * Skip test by default * pr comments * Update overrides (#10) --------- Co-authored-by: Julio César Rocha --- ...-c3827e14-777b-475a-bf00-dc169bf89f3d.json | 7 ++ .../RNTesterHeadlessTests.cpp | 30 ++++++++ .../Modules/IWebSocketModuleContentHandler.h | 15 ++++ vnext/Shared/Modules/WebSocketModule.cpp | 11 ++- .../Shared/Networking/DefaultBlobResource.cpp | 37 +++++++++ vnext/Shared/Networking/DefaultBlobResource.h | 12 +++ vnext/overrides.json | 4 + .../WebSocketArrayBufferTest.js | 76 +++++++++++++++++++ 8 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json create mode 100644 vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js diff --git a/change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json b/change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json new file mode 100644 index 00000000000..e48ac161fcd --- /dev/null +++ b/change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix WebSocket binaryType handling — stop unconditional Blob interception of binary messages", + "packageName": "react-native-windows", + "email": "gordomacmaster@gmail.com", + "dependentChangeType": "patch" +} \ No newline at end of file diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index f25eb3cdd4d..5948e5b9fb8 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -63,6 +63,36 @@ TEST_CLASS (RNTesterHeadlessTests) { auto status = TestModule::AwaitCompletion(); Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); } + + BEGIN_TEST_METHOD_ATTRIBUTE(WebSocketArrayBuffer) + TEST_IGNORE() + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(WebSocketArrayBuffer) { + TestModule::Reset(); + + winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; + bool instanceFailed{false}; + + auto holder = TestReactNativeHostHolder( + L"IntegrationTests/WebSocketArrayBufferTest", + [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { + host.InstanceSettings().InstanceLoaded( + [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { + instanceFailed = args.Failed(); + SetEvent(instanceLoadedEvent.get()); + }); + }); + + WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); + if (instanceFailed) { + auto err = holder.GetLastError(); + auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); + Assert::Fail(msg.c_str()); + } + + auto status = TestModule::AwaitCompletion(); + Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + } }; } // namespace Microsoft::React::Test diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index 4d508603865..83a22841f46 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -18,11 +18,26 @@ namespace Microsoft::React { struct IWebSocketModuleContentHandler { virtual ~IWebSocketModuleContentHandler() noexcept {} + /// Returns true if this handler should process messages for the given socket. + virtual bool CanHandleSocket(int64_t socketId) noexcept = 0; + virtual void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; virtual void ProcessMessage( std::vector &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; + + /// Check CanHandleSocket() then ProcessMessage() in one call. + /// Returns true if the message was handled. + virtual bool TryProcessMessage( + int64_t socketId, + std::string &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; + + virtual bool TryProcessMessage( + int64_t socketId, + std::vector &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index 7b0f5bb12a9..2d91ac86a40 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -83,6 +83,7 @@ shared_ptr WebSocketTurboModule::CreateResource(int64_t id, if (auto prop = propBag.Get(BlobModuleContentHandlerPropertyId())) contentHandler = prop.Value().lock(); + bool handled = false; if (contentHandler) { if (isBinary) { auto buffer = CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(message)); @@ -90,11 +91,15 @@ shared_ptr WebSocketTurboModule::CreateResource(int64_t id, CryptographicBuffer::CopyToByteArray(buffer, arr); auto data = vector(arr.begin(), arr.end()); - contentHandler->ProcessMessage(std::move(data), args); + handled = contentHandler->TryProcessMessage(id, std::move(data), args); } else { - contentHandler->ProcessMessage(string{message}, args); + handled = contentHandler->TryProcessMessage(id, string{message}, args); } - } else { + } + // When the content handler processes the message, it takes ownership of the + // payload and populates args itself (e.g. as a blob reference), so we only + // fall back to setting args["data"] when no handler claimed the message. + if (!handled) { args["data"] = message; } diff --git a/vnext/Shared/Networking/DefaultBlobResource.cpp b/vnext/Shared/Networking/DefaultBlobResource.cpp index 31fdfd6b061..bd408c72843 100644 --- a/vnext/Shared/Networking/DefaultBlobResource.cpp +++ b/vnext/Shared/Networking/DefaultBlobResource.cpp @@ -221,6 +221,11 @@ BlobWebSocketModuleContentHandler::BlobWebSocketModuleContentHandler(shared_ptr< #pragma region IWebSocketModuleContentHandler +bool BlobWebSocketModuleContentHandler::CanHandleSocket(int64_t socketId) noexcept /*override*/ { + scoped_lock lock{m_mutex}; + return m_socketIds.find(socketId) != m_socketIds.end(); +} + void BlobWebSocketModuleContentHandler::ProcessMessage( string &&message, msrn::JSValueObject ¶ms) noexcept /*override*/ @@ -241,6 +246,38 @@ void BlobWebSocketModuleContentHandler::ProcessMessage( params[blobKeys.Type] = blobKeys.Blob; } +bool BlobWebSocketModuleContentHandler::TryProcessMessage( + int64_t socketId, + string &&message, + msrn::JSValueObject ¶ms) noexcept /*override*/ +{ + scoped_lock lock{m_mutex}; + if (m_socketIds.find(socketId) == m_socketIds.end()) + return false; + + params[blobKeys.Data] = std::move(message); + return true; +} + +bool BlobWebSocketModuleContentHandler::TryProcessMessage( + int64_t socketId, + vector &&message, + msrn::JSValueObject ¶ms) noexcept /*override*/ +{ + scoped_lock lock{m_mutex}; + if (m_socketIds.find(socketId) == m_socketIds.end()) + return false; + + auto blob = msrn::JSValueObject{ + {blobKeys.Offset, 0}, + {blobKeys.Size, message.size()}, + {blobKeys.BlobId, m_blobPersistor->StoreMessage(std::move(message))}}; + + params[blobKeys.Data] = std::move(blob); + params[blobKeys.Type] = blobKeys.Blob; + return true; +} + #pragma endregion IWebSocketModuleContentHandler void BlobWebSocketModuleContentHandler::Register(int64_t socketID) noexcept { diff --git a/vnext/Shared/Networking/DefaultBlobResource.h b/vnext/Shared/Networking/DefaultBlobResource.h index 4dfdf5f18aa..9b268912462 100644 --- a/vnext/Shared/Networking/DefaultBlobResource.h +++ b/vnext/Shared/Networking/DefaultBlobResource.h @@ -51,11 +51,23 @@ class BlobWebSocketModuleContentHandler final : public IWebSocketModuleContentHa #pragma region IWebSocketModuleContentHandler + bool CanHandleSocket(int64_t socketId) noexcept override; + void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; void ProcessMessage(std::vector &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + bool TryProcessMessage( + int64_t socketId, + std::string &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + + bool TryProcessMessage( + int64_t socketId, + std::vector &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + #pragma endregion IWebSocketModuleContentHandler void Register(int64_t socketID) noexcept; diff --git a/vnext/overrides.json b/vnext/overrides.json index 833e93f73fb..b8a981838f0 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -339,6 +339,10 @@ "type": "platform", "file": "src-win/IntegrationTests/websocket_integration_test_server_blob.js" }, + { + "type": "platform", + "file": "src-win/IntegrationTests/WebSocketArrayBufferTest.js" + }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketBinaryTest.js" diff --git a/vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js b/vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js new file mode 100644 index 00000000000..50360b90b55 --- /dev/null +++ b/vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +'use strict'; + +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} + +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; + +const socket = new WebSocket(WS_URL); +socket.binaryType = 'arraybuffer'; + +socket.addEventListener('open', () => { + socket.send('hello'); +}); + +socket.addEventListener('message', event => { + const data = event.data; + + if (!(data instanceof ArrayBuffer)) { + console.log( + 'WebSocketArrayBufferTest FAIL: expected ArrayBuffer, got ' + typeof data, + ); + TestModule.markTestPassed(false); + socket.close(); + return; + } + + const bytes = new Uint8Array(data); + const expected = new Uint8Array([4, 5, 6, 7]); + + if (bytes.length !== expected.length) { + console.log( + 'WebSocketArrayBufferTest FAIL: expected ' + + expected.length + + ' bytes, got ' + + bytes.length, + ); + TestModule.markTestPassed(false); + socket.close(); + return; + } + + for (let i = 0; i < expected.length; i++) { + if (bytes[i] !== expected[i]) { + console.log( + 'WebSocketArrayBufferTest FAIL: byte[' + + i + + '] expected ' + + expected[i] + + ' got ' + + bytes[i], + ); + TestModule.markTestPassed(false); + socket.close(); + return; + } + } + + TestModule.markTestPassed(true); + socket.close(); +}); + +socket.addEventListener('error', () => { + console.log('WebSocketArrayBufferTest FAIL: WebSocket error'); + TestModule.markTestPassed(false); +}); From fca8fd95964bb8b6ed1649b3ec189b2d6237ad35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Mon, 8 Jun 2026 14:28:53 -0700 Subject: [PATCH 10/23] Upgrade projects to .NET 10 (#16226) * Upgrade projects to .NET 10 * Upgrade lock files * Change files --- ...-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json | 7 +++++++ .../Microsoft.ReactNative.CsWinRT.csproj | 2 +- .../packages.experimentalwinui3.lock.json | 2 +- .../packages.lock.json | 2 +- ...actNative.Managed.CodeGen.UnitTests.csproj | 2 +- ...crosoft.ReactNative.Managed.CodeGen.csproj | 20 +++++++++---------- .../PublishProfiles/DeployAsTool-Debug.pubxml | 2 +- .../DeployAsTool-Release.pubxml | 2 +- .../Microsoft.ReactNative.Test.Website.csproj | 2 +- vnext/TestWebSite/packages.lock.json | 2 +- 10 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json diff --git a/change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json b/change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json new file mode 100644 index 00000000000..fae64f74775 --- /dev/null +++ b/change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade projects to .NET 10", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj b/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj index 87b3d1e7fef..b96910490cb 100644 --- a/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj +++ b/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj @@ -2,7 +2,7 @@ - net8.0-windows10.0.22621.0 + net10.0-windows10.0.22621.0 10.0.22621.0 10.0.18362.0 x86;x64;arm64 diff --git a/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json b/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json index 4b18a2c7a60..37df1144e4f 100644 --- a/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json +++ b/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0-windows10.0.22621": { + "net10.0-windows10.0.22621": { "Microsoft.Windows.CsWinRT": { "type": "Direct", "requested": "[2.2.0, )", diff --git a/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json b/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json index 43b3fb364f4..0493e8c0014 100644 --- a/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json +++ b/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0-windows10.0.22621": { + "net10.0-windows10.0.22621": { "Microsoft.Windows.CsWinRT": { "type": "Direct", "requested": "[2.2.0, )", diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj b/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj index 8d5b5fa22a1..4e3c069ddf6 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 x64 win-x64 false diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj b/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj index 6b18c3aeecb..bccc8d80bdf 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj @@ -3,7 +3,7 @@ Exe - net8.0 + net10.0 x64;x86;ARM64 win-x86;win-x64 @@ -31,8 +31,8 @@ - @@ -53,7 +53,7 @@ - @@ -62,13 +62,13 @@ - - diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml index c6c28996632..910d6f9f7d6 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml @@ -6,7 +6,7 @@ FileSystem Debug x64 - net8.0 + net10.0 $(OutDir)publish win-x64 true diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml index 373853ddc0e..9973e782006 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml @@ -6,7 +6,7 @@ FileSystem Release x64 - net8.0 + net10.0 $(OutDir)publish win-x64 true diff --git a/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj b/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj index 7d08141d444..0987f9be3a6 100644 --- a/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj +++ b/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj @@ -3,7 +3,7 @@ AnyCPU AnyCPU - net8.0 + net10.0 enable enable $(IntDir)$(TargetFramework)\ diff --git a/vnext/TestWebSite/packages.lock.json b/vnext/TestWebSite/packages.lock.json index 807ab822b67..4a91a8cd78f 100644 --- a/vnext/TestWebSite/packages.lock.json +++ b/vnext/TestWebSite/packages.lock.json @@ -1,6 +1,6 @@ { "version": 1, "dependencies": { - "net8.0": {} + "net10.0": {} } } \ No newline at end of file From 00340084244e1d6ad250c775f5dcb8952b6f8195 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 15:46:57 -0700 Subject: [PATCH 11/23] Fix missing conflicts --- yarn.lock | 374 ------------------------------------------------------ 1 file changed, 374 deletions(-) diff --git a/yarn.lock b/yarn.lock index 981538c5edf..b8b5e89eaa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2627,178 +2627,16 @@ resolved "https://registry.yarnpkg.com/@react-native-windows/fs/-/fs-0.0.0-canary.71.tgz#20299b596f90f511f9ebddf62b56d2275430d57a" integrity sha512-a+MdjKRl3X9JlTQv+CO7pIdPW/8geqG37Urc4qXPgGFegDndb05XOMvR4z1qmQgKvMPDfgCbhqUFLbYXmZxoAw== dependencies: -<<<<<<< HEAD graceful-fs "^4.2.8" minimatch "^10.0.3" -||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) - "@jest/create-cache-key-function": "npm:^29.2.1" - "@jest/environment": "npm:^29.3.0" - "@jest/types": "npm:^29.2.1" - "@react-native-windows/automation-channel": "npm:0.0.0-canary.1052" - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/jest": "npm:^29.2.2" - "@types/node": "npm:^22.14.0" - "@types/readline-sync": "npm:^1.4.4" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - chalk: "npm:^4.1.2" - eslint: "npm:^8.19.0" - prettier: "npm:^3.6.2" - readline-sync: "npm:1.4.10" - typescript: "npm:5.0.4" - webdriverio: "npm:^6.9.0" - peerDependencies: - jest: ">=29.0.3" - jest-environment-node: ">=29.2.2" - languageName: unknown - linkType: soft -======= - "@jest/create-cache-key-function": "npm:^29.2.1" - "@jest/environment": "npm:^29.3.0" - "@jest/types": "npm:^29.2.1" - "@react-native-windows/automation-channel": "npm:0.0.0-canary.1052" - "@react-native-windows/find-dotnet-tools": "npm:0.0.0-canary.1" - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/jest": "npm:^29.2.2" - "@types/node": "npm:^22.14.0" - "@types/readline-sync": "npm:^1.4.4" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - chalk: "npm:^4.1.2" - eslint: "npm:^8.19.0" - prettier: "npm:^3.6.2" - readline-sync: "npm:1.4.10" - typescript: "npm:5.0.4" - webdriverio: "npm:^6.9.0" - peerDependencies: - jest: ">=29.0.3" - jest-environment-node: ">=29.2.2" - languageName: unknown - linkType: soft ->>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) "@react-native-windows/fs@^0.0.0-canary.72": version "0.0.0-canary.72" resolved "https://registry.yarnpkg.com/@react-native-windows/fs/-/fs-0.0.0-canary.72.tgz#36f877226a14c86c95739c5ca69b132ba8b819ff" integrity sha512-zM+VUo/hS0ckQ5RNcce2YDIMawEbcyxU8dZTBOTfUrURS5olkIR8ALldNsNP10IL8mU7VVNexH03+A06I+wA3g== dependencies: -<<<<<<< HEAD graceful-fs "^4.2.8" minimatch "^10.0.3" -||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) - "@babel/core": "npm:^7.25.2" - "@react-native-community/cli-doctor": "npm:20.0.0" - "@react-native-community/cli-types": "npm:20.0.0" - "@react-native-windows/codegen": "npm:0.0.0-canary.133" - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@react-native-windows/package-utils": "npm:^0.0.0-canary.98" - "@react-native-windows/telemetry": "npm:^0.0.0-canary.133" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/jest-unittest-config": "npm:1.5.12" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/chalk": "npm:^2.2.0" - "@types/jest": "npm:^29.2.2" - "@types/lodash": "npm:^4.14.168" - "@types/mustache": "npm:^4.1.1" - "@types/node": "npm:^22.14.0" - "@types/ora": "npm:^3.2.0" - "@types/prompts": "npm:2.0.10" - "@types/semver": "npm:^7.3.3" - "@types/shelljs": "npm:^0.8.8" - "@types/xml-parser": "npm:^1.2.29" - "@types/xmldom": "npm:^0.1.30" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - "@xmldom/xmldom": "npm:^0.7.7" - babel-jest: "npm:^29.6.3" - chalk: "npm:^4.1.0" - cli-spinners: "npm:^2.2.0" - envinfo: "npm:^7.5.0" - eslint: "npm:^8.19.0" - execa: "npm:^5.0.0" - find-up: "npm:^4.1.0" - glob: "npm:^7.1.1" - jest: "npm:^29.7.0" - lodash: "npm:^4.17.15" - mustache: "npm:^4.0.1" - ora: "npm:^3.4.0" - prettier: "npm:^3.6.2" - prompts: "npm:^2.4.1" - react: "npm:19.2.3" - react-native: "npm:0.85.0-nightly-20260114-f15985f4f" - semver: "npm:^7.3.2" - shelljs: "npm:^0.8.4" - typescript: "npm:5.0.4" - username: "npm:^5.1.0" - xml-formatter: "npm:^2.4.0" - xml-parser: "npm:^1.2.1" - xpath: "npm:^0.0.27" - peerDependencies: - react-native: "*" - languageName: unknown - linkType: soft -======= - "@babel/core": "npm:^7.25.2" - "@react-native-community/cli-doctor": "npm:20.0.0" - "@react-native-community/cli-types": "npm:20.0.0" - "@react-native-windows/codegen": "npm:0.0.0-canary.133" - "@react-native-windows/find-dotnet-tools": "npm:0.0.0-canary.1" - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@react-native-windows/package-utils": "npm:^0.0.0-canary.98" - "@react-native-windows/telemetry": "npm:^0.0.0-canary.133" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/jest-unittest-config": "npm:1.5.12" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/chalk": "npm:^2.2.0" - "@types/jest": "npm:^29.2.2" - "@types/lodash": "npm:^4.14.168" - "@types/mustache": "npm:^4.1.1" - "@types/node": "npm:^22.14.0" - "@types/ora": "npm:^3.2.0" - "@types/prompts": "npm:2.0.10" - "@types/semver": "npm:^7.3.3" - "@types/shelljs": "npm:^0.8.8" - "@types/xml-parser": "npm:^1.2.29" - "@types/xmldom": "npm:^0.1.30" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - "@xmldom/xmldom": "npm:^0.7.7" - babel-jest: "npm:^29.6.3" - chalk: "npm:^4.1.0" - cli-spinners: "npm:^2.2.0" - envinfo: "npm:^7.5.0" - eslint: "npm:^8.19.0" - execa: "npm:^5.0.0" - find-up: "npm:^4.1.0" - glob: "npm:^7.1.1" - jest: "npm:^29.7.0" - lodash: "npm:^4.17.15" - mustache: "npm:^4.0.1" - ora: "npm:^3.4.0" - prettier: "npm:^3.6.2" - prompts: "npm:^2.4.1" - react: "npm:19.2.3" - react-native: "npm:0.85.0-nightly-20260114-f15985f4f" - semver: "npm:^7.3.2" - shelljs: "npm:^0.8.4" - typescript: "npm:5.0.4" - username: "npm:^5.1.0" - xml-formatter: "npm:^2.4.0" - xml-parser: "npm:^1.2.1" - xpath: "npm:^0.0.27" - peerDependencies: - react-native: "*" - languageName: unknown - linkType: soft ->>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) "@react-native-windows/package-utils@^0.0.0-canary.96": version "0.0.0-canary.98" @@ -2811,69 +2649,10 @@ lodash "^4.17.15" minimatch "^10.0.3" -<<<<<<< HEAD "@react-native/assets-registry@0.84.1": version "0.84.1" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.84.1.tgz#3dbcf131b65305af36bba9df3071862643bcd78d" integrity sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg== -||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) -"@react-native-windows/find-repo-root@npm:^0.0.0-canary.101, @react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root": - version: 0.0.0-use.local - resolution: "@react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root" - dependencies: - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/find-up": "npm:^4.0.0" - "@types/node": "npm:^22.14.0" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - eslint: "npm:^8.19.0" - find-up: "npm:^4.1.0" - minimatch: "npm:^10.0.3" - prettier: "npm:^3.6.2" - typescript: "npm:5.0.4" - languageName: unknown - linkType: soft -======= -"@react-native-windows/find-dotnet-tools@npm:0.0.0-canary.1, @react-native-windows/find-dotnet-tools@workspace:packages/@react-native-windows/find-dotnet-tools": - version: 0.0.0-use.local - resolution: "@react-native-windows/find-dotnet-tools@workspace:packages/@react-native-windows/find-dotnet-tools" - dependencies: - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/node": "npm:^22.14.0" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - eslint: "npm:^8.19.0" - prettier: "npm:^3.6.2" - typescript: "npm:5.0.4" - languageName: unknown - linkType: soft - -"@react-native-windows/find-repo-root@npm:^0.0.0-canary.101, @react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root": - version: 0.0.0-use.local - resolution: "@react-native-windows/find-repo-root@workspace:packages/@react-native-windows/find-repo-root" - dependencies: - "@react-native-windows/fs": "npm:^0.0.0-canary.72" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/ts-config": "npm:2.0.6" - "@types/find-up": "npm:^4.0.0" - "@types/node": "npm:^22.14.0" - "@typescript-eslint/eslint-plugin": "npm:^8.36.0" - "@typescript-eslint/parser": "npm:^8.36.0" - eslint: "npm:^8.19.0" - find-up: "npm:^4.1.0" - minimatch: "npm:^10.0.3" - prettier: "npm:^3.6.2" - typescript: "npm:5.0.4" - languageName: unknown - linkType: soft ->>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) "@react-native/assets@1.0.0": version "1.0.0" @@ -11326,7 +11105,6 @@ react-native-platform-override@0.0.0-canary.1017: resolved "https://registry.yarnpkg.com/react-native-platform-override/-/react-native-platform-override-0.0.0-canary.1017.tgz#c94c9d8b2435a806a334a191ec6e571345fd0bb0" integrity sha512-9fM3piBVzxa1xtsVwpXaODUNZmxMOVf6kjkygnoKhdRcYTXkRC34lmJWfq18BL25wJdx9ZEZp0dGGGDwD0ujFg== dependencies: -<<<<<<< HEAD "@react-native-windows/fs" "^0.0.0-canary.70" "@react-native-windows/package-utils" "^0.0.0-canary.96" "@typescript-eslint/eslint-plugin" "^7.1.1" @@ -11346,158 +11124,6 @@ react-native-platform-override@0.0.0-canary.1017: source-map-support "^0.5.19" upath "^1.2.0" yargs "^16.2.0" -||||||| parent of b5297b99a3 (Upgrade to PowerShell 7 (#16075)) - "@babel/core": "npm:^7.25.2" - "@babel/preset-env": "npm:^7.25.3" - "@babel/runtime": "npm:^7.0.0" - "@jest/create-cache-key-function": "npm:^29.7.0" - "@react-native-community/cli": "npm:20.0.0" - "@react-native-community/cli-platform-android": "npm:20.0.0" - "@react-native-community/cli-platform-ios": "npm:20.0.0" - "@react-native-windows/cli": "npm:0.0.0-canary.288" - "@react-native-windows/codegen": "npm:0.0.0-canary.133" - "@react-native/assets": "npm:1.0.0" - "@react-native/assets-registry": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/codegen": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/community-cli-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/gradle-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/js-polyfills": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/metro-config": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/new-app-screen": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/normalize-colors": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/virtualized-lists": "npm:0.85.0-nightly-20260114-f15985f4f" - "@rnw-scripts/babel-react-native-config": "npm:0.0.0" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "npm:^1.1.42" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/metro-dev-config": "npm:0.0.0" - "@rnx-kit/jest-preset": "npm:^0.3.1" - "@types/node": "npm:^22.14.0" - "@types/react": "npm:^19.2.3" - abort-controller: "npm:^3.0.0" - anser: "npm:^1.4.9" - ansi-regex: "npm:^5.0.0" - babel-jest: "npm:^29.7.0" - babel-plugin-syntax-hermes-parser: "npm:0.32.0" - base64-js: "npm:^1.5.1" - chalk: "npm:^4.0.0" - commander: "npm:^12.0.0" - eslint: "npm:^8.19.0" - event-target-shim: "npm:^5.0.1" - flow-bin: "npm:^0.296.1" - flow-enums-runtime: "npm:^0.0.6" - glob: "npm:^7.1.1" - hermes-compiler: "npm:0.14.0-commitly-202512102158-39fca9fda" - invariant: "npm:^2.2.4" - jest-environment-node: "npm:^29.7.0" - jscodeshift: "npm:^0.14.0" - just-scripts: "npm:^1.3.3" - memoize-one: "npm:^5.0.0" - metro-runtime: "npm:^0.83.3" - metro-source-map: "npm:^0.83.3" - mkdirp: "npm:^0.5.1" - nullthrows: "npm:^1.1.1" - prettier: "npm:^3.6.2" - pretty-format: "npm:^29.7.0" - promise: "npm:^8.3.0" - react: "npm:19.2.3" - react-devtools-core: "npm:^6.1.5" - react-native: "npm:0.85.0-nightly-20260114-f15985f4f" - react-native-platform-override: "npm:0.0.0-canary.1022" - react-refresh: "npm:^0.14.0" - regenerator-runtime: "npm:^0.13.2" - scheduler: "npm:0.27.0" - semver: "npm:^7.1.3" - source-map-support: "npm:^0.5.19" - stacktrace-parser: "npm:^0.1.10" - tinyglobby: "npm:^0.2.15" - typescript: "npm:5.0.4" - whatwg-fetch: "npm:^3.0.0" - ws: "npm:^7.5.10" - yargs: "npm:^17.6.2" - peerDependencies: - "@types/react": ^19.2.3 - react: ^19.2.3 - react-native: 0.85.0-nightly-20260114-f15985f4f - languageName: unknown - linkType: soft -======= - "@babel/core": "npm:^7.25.2" - "@babel/preset-env": "npm:^7.25.3" - "@babel/runtime": "npm:^7.0.0" - "@jest/create-cache-key-function": "npm:^29.7.0" - "@react-native-community/cli": "npm:20.0.0" - "@react-native-community/cli-platform-android": "npm:20.0.0" - "@react-native-community/cli-platform-ios": "npm:20.0.0" - "@react-native-windows/cli": "npm:0.0.0-canary.288" - "@react-native-windows/codegen": "npm:0.0.0-canary.133" - "@react-native-windows/find-dotnet-tools": "npm:0.0.0-canary.1" - "@react-native/assets": "npm:1.0.0" - "@react-native/assets-registry": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/codegen": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/community-cli-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/gradle-plugin": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/js-polyfills": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/metro-config": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/new-app-screen": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/normalize-colors": "npm:0.85.0-nightly-20260114-f15985f4f" - "@react-native/virtualized-lists": "npm:0.85.0-nightly-20260114-f15985f4f" - "@rnw-scripts/babel-react-native-config": "npm:0.0.0" - "@rnw-scripts/eslint-config": "npm:1.2.38" - "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "npm:^1.1.42" - "@rnw-scripts/just-task": "npm:2.3.58" - "@rnw-scripts/metro-dev-config": "npm:0.0.0" - "@rnx-kit/jest-preset": "npm:^0.3.1" - "@types/node": "npm:^22.14.0" - "@types/react": "npm:^19.2.3" - abort-controller: "npm:^3.0.0" - anser: "npm:^1.4.9" - ansi-regex: "npm:^5.0.0" - babel-jest: "npm:^29.7.0" - babel-plugin-syntax-hermes-parser: "npm:0.32.0" - base64-js: "npm:^1.5.1" - chalk: "npm:^4.0.0" - commander: "npm:^12.0.0" - eslint: "npm:^8.19.0" - event-target-shim: "npm:^5.0.1" - flow-bin: "npm:^0.296.1" - flow-enums-runtime: "npm:^0.0.6" - glob: "npm:^7.1.1" - hermes-compiler: "npm:0.14.0-commitly-202512102158-39fca9fda" - invariant: "npm:^2.2.4" - jest-environment-node: "npm:^29.7.0" - jscodeshift: "npm:^0.14.0" - just-scripts: "npm:^1.3.3" - memoize-one: "npm:^5.0.0" - metro-runtime: "npm:^0.83.3" - metro-source-map: "npm:^0.83.3" - mkdirp: "npm:^0.5.1" - nullthrows: "npm:^1.1.1" - prettier: "npm:^3.6.2" - pretty-format: "npm:^29.7.0" - promise: "npm:^8.3.0" - react: "npm:19.2.3" - react-devtools-core: "npm:^6.1.5" - react-native: "npm:0.85.0-nightly-20260114-f15985f4f" - react-native-platform-override: "npm:0.0.0-canary.1022" - react-refresh: "npm:^0.14.0" - regenerator-runtime: "npm:^0.13.2" - scheduler: "npm:0.27.0" - semver: "npm:^7.1.3" - source-map-support: "npm:^0.5.19" - stacktrace-parser: "npm:^0.1.10" - tinyglobby: "npm:^0.2.15" - typescript: "npm:5.0.4" - whatwg-fetch: "npm:^3.0.0" - ws: "npm:^7.5.10" - yargs: "npm:^17.6.2" - peerDependencies: - "@types/react": ^19.2.3 - react: ^19.2.3 - react-native: 0.85.0-nightly-20260114-f15985f4f - languageName: unknown - linkType: soft ->>>>>>> b5297b99a3 (Upgrade to PowerShell 7 (#16075)) react-native@0.84.1: version "0.84.1" From 49e157793414c608e825510774808dfb217d8af5 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 17:02:09 -0700 Subject: [PATCH 12/23] Update packages lock --- vnext/Desktop.ABITests/packages.lock.json | 76 +++++++++++------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/vnext/Desktop.ABITests/packages.lock.json b/vnext/Desktop.ABITests/packages.lock.json index ab43a1e18e8..f889e350002 100644 --- a/vnext/Desktop.ABITests/packages.lock.json +++ b/vnext/Desktop.ABITests/packages.lock.json @@ -60,27 +60,27 @@ }, "Microsoft.WindowsAppSDK": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "AGHOiZcrDrpaxpHfEFKlI8MVnibfbSixI5DlbU6ozP/9dyWN5FkTFowg+dEOnaFRCnOzTSAjBQ1HuS4lAO+aMQ==", + "resolved": "1.8.260508005", + "contentHash": "+aA+zrvqJKgsn/1TPOSR0Uy7dkMO5jI/+cDWPu2pWmXe4KQxYkR8gQFY9IrfbgCxxSd/yl1zMcAKD/4HBbNqaw==", "dependencies": { - "Microsoft.WindowsAppSDK.AI": "[1.8.47]", + "Microsoft.WindowsAppSDK.AI": "[1.8.76]", "Microsoft.WindowsAppSDK.Base": "[1.8.251216001]", "Microsoft.WindowsAppSDK.DWrite": "[1.8.25122902]", - "Microsoft.WindowsAppSDK.Foundation": "[1.8.260203002]", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260125001]", - "Microsoft.WindowsAppSDK.ML": "[1.8.2124]", - "Microsoft.WindowsAppSDK.Runtime": "[1.8.260209005]", + "Microsoft.WindowsAppSDK.Foundation": "[1.8.260505001]", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260430001]", + "Microsoft.WindowsAppSDK.ML": "[1.8.2197]", + "Microsoft.WindowsAppSDK.Runtime": "[1.8.260508005]", "Microsoft.WindowsAppSDK.Widgets": "[1.8.251231004]", - "Microsoft.WindowsAppSDK.WinUI": "[1.8.260204000]" + "Microsoft.WindowsAppSDK.WinUI": "[1.8.260505002]" } }, "Microsoft.WindowsAppSDK.AI": { "type": "Transitive", - "resolved": "1.8.47", - "contentHash": "9il8KT8WR4T826hnm3M/USZTkPtVXFGE0IztmE1l7H9DPYsa3QHEUgGHFHQg88fsMjdr3vhyMvs23AB+1IYF1w==", + "resolved": "1.8.76", + "contentHash": "Ayn9QybcwzH+c8eQlE7dm2oO3Jrcn2uohLcsHJpCbLOAfcisjzwtSBe0oyulbaJ86R4eDSX3RDS25tsjGpIqyQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260501000" } }, "Microsoft.WindowsAppSDK.Base": { @@ -102,34 +102,34 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "Microsoft.WindowsAppSDK.InteractiveExperiences": { "type": "Transitive", - "resolved": "1.8.260125001", - "contentHash": "CTGFd1zhIDbnOltZ6piPvpNXFR1OaNyW3vHvhaILzpGziAgj5DPuVnU3PUp1p5iOBd382FLCBVM6nEPyu/LCOA==", + "resolved": "1.8.260430001", + "contentHash": "fTPCnQb3ZarMh9khlEfbLDllcZzK0tSP+2S6W/T4cPyLLSAmARm8Gd3AC5kqubnDjzTvG+1lNZ1bZGFsIHplJQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } }, "Microsoft.WindowsAppSDK.ML": { "type": "Transitive", - "resolved": "1.8.2124", - "contentHash": "l7ZptLbvOWHEJgxZtCQhUzDNCakNcqSJyAa7DNXBLKxGIUMDqq9LnWyYRZZFNQwN7hRfDAR8fEAblP1UHYHGgw==", + "resolved": "1.8.2197", + "contentHash": "6Bc1SOLd5HicY3GbF+zr76YBfH4iZOKeEGxTK/lHKAK2ExZTWavOADxB2CKSi/irF4dWSngUdRFopWPmckJ6fA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001" } }, "Microsoft.WindowsAppSDK.Runtime": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "aZjMu/glUGjzACowzzhj9drn/Ddfp1yA+f7CFXpkiSk6iZ2x32vhKfcqT64RpJ6R+Dj1hl9/79aXFhIavYNj9g==", + "resolved": "1.8.260508005", + "contentHash": "2JqXzA4heHSkkaXJNyEvYtpv2wsEUMhwQzgXmZsFmYSkUXSdeVc6hdHcWoBK1SY9l1Sjd1brLt7h16kIpgh2kA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } @@ -144,13 +144,13 @@ }, "Microsoft.WindowsAppSDK.WinUI": { "type": "Transitive", - "resolved": "1.8.260204000", - "contentHash": "DSpA01+iPXwky4O1uZCrdClSi2aRIYTIhmsTeC1EsJmWBFpSirwNAg4EGHejijV6u4ZVkTdyv3px0Y2P3fp72Q==", + "resolved": "1.8.260505002", + "contentHash": "/hGl6EOmo8aeJR2bYbOmGhOicnJK4EY+u+cQI+jm8H1uXThfMX32DsfKOt9LkOcu8AKX3j5FpiBay8ObUSytIw==", "dependencies": { "Microsoft.Web.WebView2": "1.0.3179.45", "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260203002", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "ReactNative.V8Jsi.Windows": { @@ -188,7 +188,7 @@ "FollyWin32": "[1.0.0, )", "Microsoft.JavaScript.Hermes": "[0.0.0-2605.6002-2279da22, )", "Microsoft.SourceLink.GitHub": "[1.1.1, )", - "Microsoft.WindowsAppSDK": "[1.8.260209005, )", + "Microsoft.WindowsAppSDK": "[1.8.260508005, )", "ReactCommon": "[1.0.0, )", "ReactNative.V8Jsi.Windows": "[0.71.8, )", "boost": "[1.84.0, )" @@ -220,11 +220,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -236,11 +236,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -252,11 +252,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -268,11 +268,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } } From 447c038fd3c5dfc4ba127c4dc7fac6c7d6018813 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 17:25:45 -0700 Subject: [PATCH 13/23] Remove extra yarn step --- .ado/templates/strict-yarn-install.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.ado/templates/strict-yarn-install.yml b/.ado/templates/strict-yarn-install.yml index 5359482d151..80e10e5ee67 100644 --- a/.ado/templates/strict-yarn-install.yml +++ b/.ado/templates/strict-yarn-install.yml @@ -24,7 +24,5 @@ steps: displayName: Install Node base packages - script: npx --yes midgard-yarn-strict@1.2.4 ${{ parameters.workspace }} - - - script: yarn install --immutable displayName: Strict yarn install ${{ parameters.workspace }} retryCountOnTaskFailure: 2 From e9bfd6d2403666d5cf492ba8c7f53eabb3a6e941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Mon, 8 Jun 2026 17:30:50 -0700 Subject: [PATCH 14/23] Re-introduce JS Desktop integration tests (#16228) * Remove redundant condition Desktop.IntegrationTests.SkipRNTester Modify Desktop.IntegrationTests.Filter instead. * Re-introduce WebSocket and WebSocketBlob tests * Make WSBlob test compare contents * Rename method to RunTest * Handle initial message from default endpoint * Change files * Migrate XHR test * Port Fetch test * Port Blob test * WebSocketMultipleSendTest * Remove JS WebSocket servers * Skip in CI for now --- .ado/build-template.yml | 1 + .ado/jobs/desktop-single.yml | 6 - ...-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json | 7 + .../RNTesterHeadlessTests.cpp | 104 ++++---- vnext/TestWebSite/wwwroot/static/sample.txt | 2 +- vnext/overrides.json | 12 +- vnext/src-win/IntegrationTests/BlobTest.js | 161 +++++------- vnext/src-win/IntegrationTests/FetchTest.js | 57 ++--- .../IntegrationTests/WebSocketBlobTest.js | 232 ++++++++---------- .../WebSocketMultipleSendTest.js | 199 ++++++--------- .../src-win/IntegrationTests/WebSocketTest.js | 82 +++++++ vnext/src-win/IntegrationTests/XHRTest.js | 89 ++----- ...ebsocket_integration_test_server_binary.js | 33 --- .../websocket_integration_test_server_blob.js | 33 --- 14 files changed, 423 insertions(+), 595 deletions(-) create mode 100644 change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json create mode 100644 vnext/src-win/IntegrationTests/WebSocketTest.js delete mode 100644 vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js delete mode 100644 vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js diff --git a/.ado/build-template.yml b/.ado/build-template.yml index 3b4638f825d..9b0ced95c05 100644 --- a/.ado/build-template.yml +++ b/.ado/build-template.yml @@ -281,6 +281,7 @@ extends: #12714 - Disable for first deployment of test website. - name: Desktop.IntegrationTests.Filter value: > + (FullyQualifiedName!~Microsoft::React::Test::RNTesterHeadlessTests)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& diff --git a/.ado/jobs/desktop-single.yml b/.ado/jobs/desktop-single.yml index 38d615d27b3..a733ee8cbd7 100644 --- a/.ado/jobs/desktop-single.yml +++ b/.ado/jobs/desktop-single.yml @@ -59,12 +59,6 @@ steps: - template: ../templates/apply-published-version-vars.yml - - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: - - pwsh: | - $newValue = '(FullyQualifiedName!~Microsoft::React::Test::RNTesterHeadlessTests::)&' + "$(Desktop.IntegrationTests.Filter)" - Write-Host "##vso[task.setvariable variable=Desktop.IntegrationTests.Filter]$newValue" - displayName: Update Desktop.IntegrationTests.Filter to exclude RNTester integration tests - - template: ../templates/msbuild-sln.yml parameters: solutionDir: vnext diff --git a/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json b/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json new file mode 100644 index 00000000000..dc96d8037a6 --- /dev/null +++ b/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Re-introduce WebSocket JS integration tests", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index 5948e5b9fb8..c819059c280 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "Modules/TestModule.h" #include "TestReactNativeHostHolder.h" @@ -17,6 +18,35 @@ namespace msrn = winrt::Microsoft::ReactNative; namespace Microsoft::React::Test { +namespace { +void RunTest(std::wstring_view jsBundle) { + TestModule::Reset(); + + winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; + bool instanceFailed{false}; + + auto holder = TestReactNativeHostHolder( + jsBundle, [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { + host.InstanceSettings().InstanceLoaded( + [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { + instanceFailed = args.Failed(); + SetEvent(instanceLoadedEvent.get()); + }); + }); + + // First, wait for instance to load + WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); + if (instanceFailed) { + auto err = holder.GetLastError(); + auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); + Assert::Fail(msg.c_str()); + } + + auto status = TestModule::AwaitCompletion(); + Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); +} +} // namespace + TEST_CLASS (RNTesterHeadlessTests) { TEST_CLASS_INITIALIZE(Initialize) { // https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/win32/mddbootstrap/nf-mddbootstrap-mddbootstrapinitialize2 @@ -37,61 +67,35 @@ TEST_CLASS (RNTesterHeadlessTests) { } TEST_METHOD(Dummy) { - TestModule::Reset(); - - winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; - bool instanceFailed{false}; - - auto holder = TestReactNativeHostHolder( - L"IntegrationTests/DummyTest", - [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { - host.InstanceSettings().InstanceLoaded( - [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { - instanceFailed = args.Failed(); - SetEvent(instanceLoadedEvent.get()); - }); - }); - - // First, wait for instance to load - WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); - if (instanceFailed) { - auto err = holder.GetLastError(); - auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); - Assert::Fail(msg.c_str()); - } + RunTest(L"IntegrationTests/DummyTest"); + } + + TEST_METHOD(Fetch) { + RunTest(L"IntegrationTests/FetchTest"); + } - auto status = TestModule::AwaitCompletion(); - Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + TEST_METHOD(XHR) { + RunTest(L"IntegrationTests/XHRTest"); + } + + TEST_METHOD(WebSocket) { + RunTest(L"IntegrationTests/WebSocketTest"); + } + + TEST_METHOD(WebSocketBlob) { + RunTest(L"IntegrationTests/WebSocketBlobTest"); } - BEGIN_TEST_METHOD_ATTRIBUTE(WebSocketArrayBuffer) - TEST_IGNORE() - END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(WebSocketArrayBuffer) { - TestModule::Reset(); - - winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; - bool instanceFailed{false}; - - auto holder = TestReactNativeHostHolder( - L"IntegrationTests/WebSocketArrayBufferTest", - [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { - host.InstanceSettings().InstanceLoaded( - [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { - instanceFailed = args.Failed(); - SetEvent(instanceLoadedEvent.get()); - }); - }); - - WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); - if (instanceFailed) { - auto err = holder.GetLastError(); - auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); - Assert::Fail(msg.c_str()); - } + RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); + } + + TEST_METHOD(WebSocketMultipleSend) { + RunTest(L"IntegrationTests/WebSocketMultipleSendTest"); + } - auto status = TestModule::AwaitCompletion(); - Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + TEST_METHOD(Blob) { + RunTest(L"IntegrationTests/BlobTest"); } }; diff --git a/vnext/TestWebSite/wwwroot/static/sample.txt b/vnext/TestWebSite/wwwroot/static/sample.txt index 5bd282c8887..47e646f426f 100644 --- a/vnext/TestWebSite/wwwroot/static/sample.txt +++ b/vnext/TestWebSite/wwwroot/static/sample.txt @@ -1 +1 @@ -Sample Static Text File +Sample Static Text File \ No newline at end of file diff --git a/vnext/overrides.json b/vnext/overrides.json index b8a981838f0..a2e5329aefa 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -331,14 +331,6 @@ "type": "platform", "file": "src-win/IntegrationTests/LoggingTest.js" }, - { - "type": "platform", - "file": "src-win/IntegrationTests/websocket_integration_test_server_binary.js" - }, - { - "type": "platform", - "file": "src-win/IntegrationTests/websocket_integration_test_server_blob.js" - }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketArrayBufferTest.js" @@ -351,6 +343,10 @@ "type": "platform", "file": "src-win/IntegrationTests/WebSocketBlobTest.js" }, + { + "type": "platform", + "file": "src-win/IntegrationTests/WebSocketTest.js" + }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketMultipleSendTest.js" diff --git a/vnext/src-win/IntegrationTests/BlobTest.js b/vnext/src-win/IntegrationTests/BlobTest.js index 47cf5ecb81e..363ee3c99f7 100644 --- a/vnext/src-win/IntegrationTests/BlobTest.js +++ b/vnext/src-win/IntegrationTests/BlobTest.js @@ -2,115 +2,78 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -type State = { - statusCode: number, - xhr: XMLHttpRequest, - expected: string, -}; - -class BlobTest extends React.Component<{...}, State> { - state: State = { - statusCode: 0, - xhr: new XMLHttpRequest(), - // http://localhost:5555/static/react.png - // cspell:disable - expected: - 'data:application/octet-stream;base64,' + - 'iVBORw0KGgoAAAANSUhEUgAAABcAAAAVCAYAAACt4nWrAAABbWlDQ1BpY2MAACiRdZG9S0JR' + - 'GMZ/alGk4VBDRJCDRYNCFEhj2dAiIWaQ1aLXr0Cvl3uViNagpUFoiFr6GvoPag1aC4KgCCLa' + - '2vtaQm7vUUGJOpdz3x/POc/Le58LzkhBK1od01DUy2ZsLuxbSiz7ul5x48RFiOGkZhkz0WiE' + - 'f9fXPQ5V74Kq1//3/lzudMbSwNEtHNIMsyws0xBZLxuKd4T7tXwyLXwkHDBlQOFrpaca/KI4' + - '1+APxWY8NgtO1dOXa+NUG2t5syg8JuwvFipacx71JZ6MvrggdVD2EBYx5gjjI0WFNQqUCUrV' + - 'JbO/feN13zwl8WjyNtjAFEeOvHgDolaka0ZqVvSMPAU2VO6/87SykxON7p4wdD7b9vsIdO1C' + - 'rWrb38e2XTsB1xNc6i1/SXKa+hS92tL8h+DdgvOrlpbag4ttGHg0kmayLrlkO7NZeDuD3gT0' + - '3ULPSiOr5jmnDxDflF90A/sHMCr3vas/DkpoELlQjWUAAAAJcEhZcwAAnXsAAJ17ATyfd8QA' + - 'AARxSURBVDgRbVRbbFRVFN373Hs7fSBorI/S2FLK9GETQgHjBwlg+fABQT78METKTBtrookR' + - 'QgJFDcUP2wRiUKMfVZnCxBq1Rk0MyA81fjQxhQKJ2naGUDum8a1AW9qZe+/ZrjP0NreOJzmz' + - 'z36te86avTfT/6y2tHtIhPbD5WIPiGN399Xwr0FobELuZ9frhP4UtsNMb5yMOj2BP5AqOASy' + - 'PZ1rBvBeYrsJoE2wT3POuxRPeTtMjJFGx/Fm3m/iEB9L5dYZf3jZYcWcRXOLECX76viPBd8r' + - 'e9PZj5SogXja3Uoi20npbYlo5McgNzbuJhXxNuiXA5uRBTfXROWK83Qsxp2KRn4QVm+T8IvE' + - '6q0wsAkCLZ7JW0xYOBSAK0U3NFNpOBA33sKiO4W87XjaYaOH/SxUavLCNnMuoIU0X1GsW48M' + - 'ij1Zla1R2qoxNIEOPN1ai/PHxPRJLOU+Q5b/U3UmMvEzu/Wa+dR/wTlsiI/PrRZVtBO37AbA' + - 'LAn9gl2Od8/g6YP4R6ZBwh0iukWEy2D7EwAV+GAZaOu0/dyX7zeUTASYefD2a1KtXfcYAB9i' + - '5n4A7mCL9vs+r2CSHqfMXtdbybeCpD1XpMwu8S8D9CB5Mq0sOU6KvxKR3cgd9mznQLKWM6pt' + - 'LLfR9/xBVjQkjtOIen0ZnPeLx0/iy8eEqaN3Jc3Fx92j2BeMTK6lW6ykw/jZ4p3C/KHJM/kG' + - 'x/L9bwwux1PuCGk6kGhwzgc3ax2Vuy3Lvwaev0/UO5vaUrmYECcCP14TP1lX1BdPe0OIafJ9' + - 'e/XpRv4r8MfH3BbU4XFTLdUSsUcCh5GnG+hviCwAM0aHrDUyWCE9A2d2IT5w0wJeFaMBulFG' + - 'DbayY721nC8nlNqjSHod0atUzo761vxytuzvoN+L/bv43sOaiqcty0uBmklQ15mIOucMOkbD' + - 'ncrzElrTGJuSy1R6XbA/Syw9YjsJdt0TLOo8kjbB7ifq7OfzSb7XrC37EubMdYyBd+GzcIkh' + - 'xG0lx9pHOa8NFXQQ9veqpuyufLVAofar82t8sV9CQzyGRqkSpZIW6Ytacxca+TVluV9ridxQ' + - 'nF3ha/U4C79qKTnikdrIWu9B52ZQbWcVe29+sKb4qsFcBDeKWfF09kES1S8svaTVejDeiNts' + - 'QOgUzsshb+IPrcSwugjuRzFnRnD753ySp5N1kdHbKLd/CzoUN9qM+j3XFy0yz84vVNQJIn3P' + - 'TNRpXZZ2k5r4t756Z1/gR7eucoi2QF8CXjBbkLgSN7keJBo5U2J3YmhVLEt7F9Ct982W2IfD' + - 'fhOPjq1YYoNSAA7O0ckyFw789AGeA4HDoKca9uG8Hgow8eAXD166CsANj4hqDsJax6UStHwG' + - 'fb1datcBYgNoGDD2IIY1NePFY4EeyII/tGNKSt1ZbwQz5lst5LPILtz6HZRWz9FH2Fso3UP4' + - 'yAto+y8w+y3MlM2grrngRcFXwrJjXMpdcnexUnPzRdaZ/mr+J+w3592Tcldxzn9CtC7xtfN5' + - 'uP2D2H8BsGL5W7blULYAAAAASUVORK5CYII=', - // cspell:enable - }; +if (!TestModule) { + throw new Error('TestModule is not available'); +} - _get = () => { - this.state.xhr.onloadend = () => { - this.setState({ - statusCode: this.state.xhr.status, - }); - }; - this.state.xhr.open('GET', 'http://localhost:5555/static/react.png'); - this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.state.xhr.responseType = 'blob'; - this.state.xhr.send(); - }; +const URL = 'http://localhost:5555/static/react.png'; - _getSucceeded = (): boolean => { - return this.state.statusCode === 200 && this.state.xhr.response !== null; - }; +// cspell:disable +const EXPECTED_DATA_URL = + 'data:application/octet-stream;base64,' + + 'iVBORw0KGgoAAAANSUhEUgAAABcAAAAVCAYAAACt4nWrAAABbWlDQ1BpY2MAACiRdZG9S0JR' + + 'GMZ/alGk4VBDRJCDRYNCFEhj2dAiIWaQ1aLXr0Cvl3uViNagpUFoiFr6GvoPag1aC4KgCCLa' + + '2vtaQm7vUUGJOpdz3x/POc/Le58LzkhBK1od01DUy2ZsLuxbSiz7ul5x48RFiOGkZhkz0WiE' + + 'f9fXPQ5V74Kq1//3/lzudMbSwNEtHNIMsyws0xBZLxuKd4T7tXwyLXwkHDBlQOFrpaca/KI4' + + '1+APxWY8NgtO1dOXa+NUG2t5syg8JuwvFipacx71JZ6MvrggdVD2EBYx5gjjI0WFNQqUCUrV' + + 'JbO/feN13zwl8WjyNtjAFEeOvHgDolaka0ZqVvSMPAU2VO6/87SykxON7p4wdD7b9vsIdO1C' + + 'rWrb38e2XTsB1xNc6i1/SXKa+hS92tL8h+DdgvOrlpbag4ttGHg0kmayLrlkO7NZeDuD3gT0' + + '3ULPSiOr5jmnDxDflF90A/sHMCr3vas/DkpoELlQjWUAAAAJcEhZcwAAnXsAAJ17ATyfd8QA' + + 'AARxSURBVDgRbVRbbFRVFN373Hs7fSBorI/S2FLK9GETQgHjBwlg+fABQT78METKTBtrookR' + + 'QgJFDcUP2wRiUKMfVZnCxBq1Rk0MyA81fjQxhQKJ2naGUDum8a1AW9qZe+/ZrjP0NreOJzmz' + + 'z36te86avTfT/6y2tHtIhPbD5WIPiGN399Xwr0FobELuZ9frhP4UtsNMb5yMOj2BP5AqOASy' + + 'PZ1rBvBeYrsJoE2wT3POuxRPeTtMjJFGx/Fm3m/iEB9L5dYZf3jZYcWcRXOLECX76viPBd8r' + + 'e9PZj5SogXja3Uoi20npbYlo5McgNzbuJhXxNuiXA5uRBTfXROWK83Qsxp2KRn4QVm+T8IvE' + + '6q0wsAkCLZ7JW0xYOBSAK0U3NFNpOBA33sKiO4W87XjaYaOH/SxUavLCNnMuoIU0X1GsW48M' + + 'ij1Zla1R2qoxNIEOPN1ai/PHxPRJLOU+Q5b/U3UmMvEzu/Wa+dR/wTlsiI/PrRZVtBO37AbA' + + 'LAn9gl2Od8/g6YP4R6ZBwh0iukWEy2D7EwAV+GAZaOu0/dyX7zeUTASYefD2a1KtXfcYAB9i' + + '5n4A7mCL9vs+r2CSHqfMXtdbybeCpD1XpMwu8S8D9CB5Mq0sOU6KvxKR3cgd9mznQLKWM6pt' + + 'LLfR9/xBVjQkjtOIen0ZnPeLx0/iy8eEqaN3Jc3Fx92j2BeMTK6lW6ykw/jZ4p3C/KHJM/kG' + + 'x/L9bwwux1PuCGk6kGhwzgc3ax2Vuy3Lvwaev0/UO5vaUrmYECcCP14TP1lX1BdPe0OIafJ9' + + 'e/XpRv4r8MfH3BbU4XFTLdUSsUcCh5GnG+hviCwAM0aHrDUyWCE9A2d2IT5w0wJeFaMBulFG' + + 'DbayY721nC8nlNqjSHod0atUzo761vxytuzvoN+L/bv43sOaiqcty0uBmklQ15mIOucMOkbD' + + 'ncrzElrTGJuSy1R6XbA/Syw9YjsJdt0TLOo8kjbB7ifq7OfzSb7XrC37EubMdYyBd+GzcIkh' + + 'xG0lx9pHOa8NFXQQ9veqpuyufLVAofar82t8sV9CQzyGRqkSpZIW6Ytacxca+TVluV9ridxQ' + + 'nF3ha/U4C79qKTnikdrIWu9B52ZQbWcVe29+sKb4qsFcBDeKWfF09kES1S8svaTVejDeiNts' + + 'QOgUzsshb+IPrcSwugjuRzFnRnD753ySp5N1kdHbKLd/CzoUN9qM+j3XFy0yz84vVNQJIn3P' + + 'TNRpXZZ2k5r4t756Z1/gR7eucoi2QF8CXjBbkLgSN7keJBo5U2J3YmhVLEt7F9Ct982W2IfD' + + 'fhOPjq1YYoNSAA7O0ckyFw789AGeA4HDoKca9uG8Hgow8eAXD166CsANj4hqDsJax6UStHwG' + + 'fb1datcBYgNoGDD2IIY1NePFY4EeyII/tGNKSt1ZbwQz5lst5LPILtz6HZRWz9FH2Fso3UP4' + + 'yAto+y8w+y3MlM2grrngRcFXwrJjXMpdcnexUnPzRdaZ/mr+J+w3592Tcldxzn9CtC7xtfN5' + + 'uP2D2H8BsGL5W7blULYAAAAASUVORK5CYII='; +// cspell:enable - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +const xhr = new XMLHttpRequest(); +xhr.responseType = 'blob'; - componentDidMount() { - this._get(); - this._waitFor(this._getSucceeded, 6, doneSucceeded => { - let reader = new FileReader(); - reader.readAsDataURL(this.state.xhr.response); - reader.onload = () => { - TestModule.markTestPassed( - doneSucceeded && this.state.expected === reader.result, - ); - }; - }); +xhr.onloadend = () => { + const succeeded = xhr.status === 200 && xhr.response !== null; + if (!succeeded) { + TestModule.markTestPassed(false); + return; } - render(): React.Node { - return ; - } -} + const reader = new FileReader(); + reader.onload = () => { + TestModule.markTestPassed(reader.result === EXPECTED_DATA_URL); + }; + reader.onerror = () => { + TestModule.markTestPassed(false); + }; + reader.readAsDataURL(xhr.response); +}; -AppRegistry.registerComponent('BlobTest', () => BlobTest); +xhr.onerror = () => { + TestModule.markTestPassed(false); +}; -module.exports = BlobTest; +xhr.open('GET', URL); +xhr.setRequestHeader('Accept-Encoding', 'utf-8'); +xhr.send(); diff --git a/vnext/src-win/IntegrationTests/FetchTest.js b/vnext/src-win/IntegrationTests/FetchTest.js index 79c666fa581..ff645f5516a 100644 --- a/vnext/src-win/IntegrationTests/FetchTest.js +++ b/vnext/src-win/IntegrationTests/FetchTest.js @@ -2,52 +2,27 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -const uri = - 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/.yarnrc.yml'; -const expectedContent = 'enableScripts: false'; - -type State = { - uri: string, - expected: string, - content: string, -}; - -class FetchTest extends React.Component<{...}, State> { - state: State = { - uri: 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/.yarnrc.yml', - expected: 'enableScripts: false', - content: '', - }; - - async componentDidMount() { - const response = await fetch(uri); - const text = await response.text(); - this.setState({content: text}); - - if (this.state.content === expectedContent) { - TestModule.markTestPassed(true); - } else { - TestModule.markTestPassed(false); - } - } - - render(): React.Node { - return ; - } +if (!TestModule) { + throw new Error('TestModule is not available'); } -AppRegistry.registerComponent('FetchTest', () => FetchTest); - -module.exports = FetchTest; +const URL = 'http://localhost:5555/static/sample.txt'; +const EXPECTED_CONTENT = 'Sample Static Text File'; + +fetch(URL) + .then(response => response.text()) + .then(text => { + const passed = text === EXPECTED_CONTENT; + TestModule.markTestPassed(passed); + }) + .catch(() => { + TestModule.markTestPassed(false); + }); diff --git a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js index 7b18bfe2c07..1b7b675276b 100644 --- a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js @@ -1,159 +1,121 @@ /** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); -// eslint-disable-next-line @microsoft/sdl/no-insecure-url -const DEFAULT_WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; - -const WS_EVENTS = ['close', 'error', 'message', 'open']; - -type State = { - url: string, - fetchStatus: ?string, - socket: ?WebSocket, - socketState: ?number, - lastSocketEvent: ?string, - lastMessage: ?Blob, - testMessage: Uint8Array, - testExpectedResponse: Uint8Array, - ... -}; - -class WebSocketBlobTest extends React.Component<{}, State> { - state: State = { - url: DEFAULT_WS_URL, - fetchStatus: null, - socket: null, - socketState: null, - lastSocketEvent: null, - lastMessage: null, - testMessage: new Uint8Array([1, 2, 3]), - testExpectedResponse: new Uint8Array([4, 5, 6, 7]), - }; +if (!TestModule) { + throw new Error('TestModule is not available'); +} - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; +const TEST_MESSAGE = new Uint8Array([1, 2, 3]); +// Expected bytes come from RNTesterIntegrationTests.WebSocketBinaryTest outgoingBytes. +const EXPECTED_BYTES = new Uint8Array([4, 5, 6, 7]); +const EXPECTED_SIZE = EXPECTED_BYTES.length; + +let completed = false; +const socket = new WebSocket(WS_URL); +socket.binaryType = 'blob'; + +const timeoutId = setTimeout(() => { + complete(false, 'timeout waiting for websocket response'); +}, 10000); + +function complete(passed, reason) { + if (completed) { + return; + } - _connect = () => { - const socket = new WebSocket(this.state.url); - socket.binaryType = 'blob'; - WS_EVENTS.forEach(ev => socket.addEventListener(ev, this._onSocketEvent)); - this.setState({ - socket, - socketState: socket.readyState, - }); - }; + completed = true; + clearTimeout(timeoutId); - _socketIsConnected = (): boolean => { - return this.state.socketState === 1; //'OPEN' - }; + if (!passed && reason) { + console.log('WebSocketBlobTest FAIL: ' + reason); + } - _socketIsDisconnected = (): boolean => { - return this.state.socketState === 3; //'CLOSED' - }; + TestModule.markTestPassed(passed); +} - _disconnect = () => { - if (!this.state.socket) { - return; - } - this.state.socket.close(); - }; +function decodeBlobToBytes(blob, onSuccess, onError) { + if (typeof blob.arrayBuffer === 'function') { + blob + .arrayBuffer() + .then(buffer => onSuccess(new Uint8Array(buffer))) + .catch(onError); + return; + } - _onSocketEvent = (event: any) => { - const state: any = { - socketState: event.target.readyState, - lastSocketEvent: event.type, - }; - if (event.type === 'message') { - state.lastMessage = event.data; - } - this.setState(state); + const reader = new FileReader(); + reader.onload = () => { + onSuccess(new Uint8Array(reader.result)); }; - - _sendBinary = (message: Uint8Array) => { - if (!this.state.socket) { - return; - } - this.state.socket.send(message); + reader.onerror = () => { + onError(reader.error || new Error('failed to read blob')); }; + reader.readAsArrayBuffer(blob); +} - _sendTestMessage = () => { - this._sendBinary(this.state.testMessage); - }; +socket.addEventListener('open', () => { + socket.send(TEST_MESSAGE); +}); - _receivedTestExpectedResponse = (): boolean => { - // Can't iterate through Blob response. Blob.arrayBuffer() not supported. - return ( - this.state.lastMessage?.size === this.state.testExpectedResponse.length - ); - }; +socket.addEventListener('message', event => { + const data = event.data; - componentDidMount() { - this.testConnect(); + if (!(data instanceof Blob)) { + complete(false, 'expected Blob response'); + socket.close(); + return; } - testConnect: () => void = () => { - this._connect(); - this._waitFor(this._socketIsConnected, 5, connectSucceeded => { - if (!connectSucceeded) { - TestModule.markTestPassed(false); - return; - } - this.testSendAndReceive(); - }); - }; + if (data.size !== EXPECTED_SIZE) { + complete(false, 'unexpected response size'); + socket.close(); + return; + } - testSendAndReceive: () => void = () => { - this._sendTestMessage(); - this._waitFor(this._receivedTestExpectedResponse, 5, messageReceived => { - if (!messageReceived) { - TestModule.markTestPassed(false); - return; + decodeBlobToBytes( + data, + bytes => { + for (let i = 0; i < EXPECTED_BYTES.length; i++) { + if (bytes[i] !== EXPECTED_BYTES[i]) { + complete( + false, + 'unexpected response byte at index ' + + i + + ': expected ' + + EXPECTED_BYTES[i] + + ', got ' + + bytes[i], + ); + socket.close(); + return; + } } - this.testDisconnect(); - }); - }; - testDisconnect: () => void = () => { - this._disconnect(); - this._waitFor(this._socketIsDisconnected, 5, disconnectSucceeded => { - TestModule.markTestPassed(disconnectSucceeded); - }); - }; - render(): React.Node { - return ; + complete(true); + socket.close(); + }, + error => { + complete(false, 'failed to decode blob: ' + String(error)); + socket.close(); + }, + ); +}); + +socket.addEventListener('error', () => { + complete(false, 'websocket error'); +}); + +socket.addEventListener('close', () => { + if (!completed) { + complete(false, 'socket closed before completing test'); } -} // class WebSocketBlobTest - -WebSocketBlobTest.displayName = 'WebSocketBlobTest'; - -AppRegistry.registerComponent('WebSocketBlobTest', () => WebSocketBlobTest); - -module.exports = WebSocketBlobTest; +}); diff --git a/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js b/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js index e3658cdab96..afaa789368f 100644 --- a/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js @@ -10,7 +10,6 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow */ /* @@ -25,10 +24,13 @@ Array.from('abcdef').forEach( e => { ws.send(e.repeat(1025)) }); 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TurboModuleRegistry} = require('react-native'); + +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} // eslint-disable-next-line @microsoft/sdl/no-insecure-url const URL_BASE = 'ws://localhost:5555/rnw/rntester/websocketmultiplesendtest'; @@ -41,131 +43,90 @@ const EXPECTED = 'abcdef'; const ID = Math.floor(Math.random() * 1000000); -type State = { - sendUrl: string, - receiveUrl: string, - sendSocket: ?WebSocket, - receiveSocket: ?WebSocket, - result: ?string, -}; - -class WebSocketMultipleSendTest extends React.Component<{}, State> { - state: State = { - sendUrl: `${URL_BASE}/send/${ID}`, - receiveUrl: `${URL_BASE}/receive/${ID}`, - sendSocket: null, - receiveSocket: null, - result: '', - }; +const sendUrl = `${URL_BASE}/send/${ID}`; +const receiveUrl = `${URL_BASE}/receive/${ID}`; - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +let sendSocket = null; +let receiveSocket = null; +let result = ''; - _socketsAreConnected = (): boolean => { - return ( - this.state.sendSocket?.readyState === 1 && - this.state.sendSocket?.readyState === 1 - ); // OPEN +const waitFor = (condition, timeout, callback) => { + let remaining = timeout; + const timeoutFunction = function () { + if (condition()) { + callback(true); + return; + } + remaining--; + if (remaining === 0) { + callback(false); + } else { + setTimeout(timeoutFunction, 1000); + } }; + setTimeout(timeoutFunction, 1000); +}; - _socketsAreDisconnected = (): boolean => { - return ( - this.state.sendSocket?.readyState === 3 && - this.state.sendSocket?.readyState === 3 - ); // CLOSED - }; +const socketsAreConnected = () => { + return sendSocket?.readyState === 1 && sendSocket?.readyState === 1; // OPEN +}; - _resultIsComplete = (): boolean => { - return this.state.result === EXPECTED; - }; +const socketsAreDisconnected = () => { + return sendSocket?.readyState === 3 && sendSocket?.readyState === 3; // CLOSED +}; - _disconnect = () => { - if (this.state.receiveSocket) { - this.state.receiveSocket.close(); - } - }; +const resultIsComplete = () => { + return result === EXPECTED; +}; - testDisconnect: () => void = () => { - this._disconnect(); - this._waitFor(this._socketsAreDisconnected, 5, disconnectSucceeded => { - TestModule.markTestPassed(disconnectSucceeded); - }); - }; +const disconnect = () => { + if (receiveSocket) { + receiveSocket.close(); + } +}; - testSendMultipleAndClose: () => void = () => { - this.state.sendSocket?.send('a'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('b'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('c'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('d'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('e'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('f'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.close(); - - this._waitFor(this._resultIsComplete, 5, resultComplete => { - if (!resultComplete) { - TestModule.markTestPassed(false); - return; - } - this.testDisconnect(); - }); - }; +const testDisconnect = () => { + disconnect(); + waitFor(socketsAreDisconnected, 5, disconnectSucceeded => { + TestModule.markTestPassed(disconnectSucceeded); + }); +}; - _onSocketEvent = (event: any) => { - if (event.type === 'message' && event.data.length) { - var message = this.state.result + event.data[0]; - this.setState({ - result: message, - }); +const testSendMultipleAndClose = () => { + sendSocket?.send('a'.repeat(MESSAGE_SIZE)); + sendSocket?.send('b'.repeat(MESSAGE_SIZE)); + sendSocket?.send('c'.repeat(MESSAGE_SIZE)); + sendSocket?.send('d'.repeat(MESSAGE_SIZE)); + sendSocket?.send('e'.repeat(MESSAGE_SIZE)); + sendSocket?.send('f'.repeat(MESSAGE_SIZE)); + sendSocket?.close(); + + waitFor(resultIsComplete, 5, resultComplete => { + if (!resultComplete) { + TestModule.markTestPassed(false); + return; } - }; - - _connect = () => { - const sendSocket = new WebSocket(this.state.sendUrl); - const receiveSocket = new WebSocket(this.state.receiveUrl); - WS_EVENTS.forEach(ev => - receiveSocket.addEventListener(ev, this._onSocketEvent), - ); - this.setState({ - sendSocket, - receiveSocket, - }); - }; - - componentDidMount() { - this._connect(); - this._waitFor(this._socketsAreConnected, 5, connectSucceeded => { - if (!connectSucceeded) { - TestModule.markTestPassed(false); - return; - } - this.testSendMultipleAndClose(); - }); - } + testDisconnect(); + }); +}; - render(): React.Node { - return ; +const onSocketEvent = event => { + if (event.type === 'message' && event.data.length) { + result += event.data[0]; } -} // class WebSocketMultipleSendTest - -WebSocketMultipleSendTest.displayName = 'WebSocketMultipleSendTest'; +}; -AppRegistry.registerComponent( - 'WebSocketMultipleSendTest', - () => WebSocketMultipleSendTest, -); +const connect = () => { + sendSocket = new WebSocket(sendUrl); + receiveSocket = new WebSocket(receiveUrl); + WS_EVENTS.forEach(ev => receiveSocket.addEventListener(ev, onSocketEvent)); +}; -module.exports = WebSocketMultipleSendTest; +connect(); +waitFor(socketsAreConnected, 5, connectSucceeded => { + if (!connectSucceeded) { + TestModule.markTestPassed(false); + return; + } + testSendMultipleAndClose(); +}); diff --git a/vnext/src-win/IntegrationTests/WebSocketTest.js b/vnext/src-win/IntegrationTests/WebSocketTest.js new file mode 100644 index 00000000000..b1975486599 --- /dev/null +++ b/vnext/src-win/IntegrationTests/WebSocketTest.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +'use strict'; + +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} + +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/'; +const TEST_MESSAGE = 'testMessage'; +const EXPECTED_RESPONSE = 'testMessage_response'; +const INITIAL_SERVER_GREETING = 'hello'; + +let completed = false; +let messageSent = false; +const socket = new WebSocket(WS_URL); +const timeoutId = setTimeout(() => { + complete(false, 'timeout waiting for websocket response'); +}, 10000); + +function sendTestMessageIfNeeded() { + if (messageSent || socket.readyState !== WebSocket.OPEN) { + return; + } + + messageSent = true; + socket.send(TEST_MESSAGE); +} + +function complete(passed, reason) { + if (completed) { + return; + } + + completed = true; + clearTimeout(timeoutId); + + if (!passed && reason) { + console.log('WebSocketTest FAIL: ' + reason); + } + + TestModule.markTestPassed(passed); +} + +socket.addEventListener('open', () => { + sendTestMessageIfNeeded(); +}); + +socket.addEventListener('message', event => { + if (event.data === INITIAL_SERVER_GREETING) { + return; + } + + if (event.data !== EXPECTED_RESPONSE) { + complete(false, 'unexpected response payload'); + socket.close(); + return; + } + + complete(true); + socket.close(); +}); + +socket.addEventListener('error', () => { + complete(false, 'websocket error'); +}); + +socket.addEventListener('close', () => { + if (!completed) { + complete(false, 'socket closed before completing test'); + } +}); + +sendTestMessageIfNeeded(); diff --git a/vnext/src-win/IntegrationTests/XHRTest.js b/vnext/src-win/IntegrationTests/XHRTest.js index a03ba97c658..2b85a9a0f3d 100644 --- a/vnext/src-win/IntegrationTests/XHRTest.js +++ b/vnext/src-win/IntegrationTests/XHRTest.js @@ -2,81 +2,30 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -type State = { - statusCode: number, - xhr: XMLHttpRequest, -}; - -class XHRTest extends React.Component<{...}, State> { - state: State = { - statusCode: 0, - xhr: new XMLHttpRequest(), - }; - - _get = () => { - this.state.xhr.onloadend = () => { - this.setState({ - statusCode: this.state.xhr.status, - }); - }; - this.state.xhr.open( - 'GET', - 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config', - ); - this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.state.xhr.send(); - }; - - _getSucceeded = (): boolean => { - console.log( - `_getSucceeded [${this.state.statusCode}],[${this.state.xhr.responseText.length}]`, - ); - return ( - this.state.statusCode === 200 && - this.state.xhr.responseText.length === 387 - ); - }; - - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; - - componentDidMount() { - this._get(); - this._waitFor(this._getSucceeded, 5, doneSucceeded => { - TestModule.markTestPassed(doneSucceeded); - }); - } - - render(): React.Node { - return ; - } +if (!TestModule) { + throw new Error('TestModule is not available'); } -AppRegistry.registerComponent('XHRTest', () => XHRTest); +const URL = 'http://localhost:5555/static/sample.txt'; +const EXPECTED_CONTENT = 'Sample Static Text File'; + +const xhr = new XMLHttpRequest(); +xhr.onloadend = () => { + const responseText = xhr.responseText || ''; + const passed = + xhr.status === 200 && responseText === EXPECTED_CONTENT; + TestModule.markTestPassed(passed); +}; +xhr.onerror = () => { + TestModule.markTestPassed(false); +}; -module.exports = XHRTest; +xhr.open('GET', URL); +xhr.send(); diff --git a/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js b/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js deleted file mode 100644 index 54583577d48..00000000000 --- a/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ - -'use strict'; - -/* eslint-env node */ - -const WebSocket = require('ws'); - -console.log(`\ -WebSocket binary integration test server - -This will send each incoming message back, in binary form. - -`); - -const server = new WebSocket.Server({port: 5557}); -server.on('connection', ws => { - ws.binaryType = 'arraybuffer'; - ws.on('message', message => { - console.log(message); - - ws.send(new Uint8Array([4, 5, 6, 7]).buffer); - }); -}); diff --git a/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js b/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js deleted file mode 100644 index d08ae16e0c5..00000000000 --- a/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ - -'use strict'; - -/* eslint-env node */ - -const WebSocket = require('ws'); - -console.log(`\ -WebSocket binary integration test server - -This will send each incoming message back, in binary form. - -`); - -const server = new WebSocket.Server({port: 5557}); -server.on('connection', ws => { - ws.binaryType = 'blob'; - ws.on('message', message => { - console.log(message); - - ws.send([4, 5, 6, 7]); - }); -}); From 32286f90d92ab66fba6a3c14cf48a9a9a4455d35 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 18:35:24 -0700 Subject: [PATCH 15/23] Remove ban-types --- packages/@rnw-scripts/eslint-config/eslintrc.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/@rnw-scripts/eslint-config/eslintrc.js b/packages/@rnw-scripts/eslint-config/eslintrc.js index bb32fcdd24e..a26b260fdaf 100644 --- a/packages/@rnw-scripts/eslint-config/eslintrc.js +++ b/packages/@rnw-scripts/eslint-config/eslintrc.js @@ -48,15 +48,6 @@ module.exports = { rules: { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-tslint-comment': 'error', - '@typescript-eslint/ban-types': [ - 'error', { - 'extendDefaults': true, - 'types': { - // See https://github.com/typescript-eslint/typescript-eslint/issues/2063 - '{}': false - } - } - ], '@typescript-eslint/no-confusing-non-null-assertion': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-floating-promises': [ From 1013c4ac7bc42f8f201af1058c73e3241071a85d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 19:32:19 -0700 Subject: [PATCH 16/23] Fix DynamicReaderWriterTests --- vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp index 4f5b68e44f7..a7f5427779e 100644 --- a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp +++ b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp @@ -166,10 +166,10 @@ TEST_CLASS (DynamicReaderWriterTests) { } private: - template + template void TestScalar( - void(IJSValueWriter::*writerMethod)(TWriterValue) const, - TReaderValue (IJSValueReader::*readerMethod)() const, + WriterMethod writerMethod, + ReaderMethod readerMethod, JSValueType runtimeType, TWriterValue value) { IJSValueWriter writer = Microsoft::Internal::TestController::CreateDynamicWriter(); From 3badb2df9b337b79e3c2953dc05998d4f80f9afb Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 20:24:16 -0700 Subject: [PATCH 17/23] Use WinRT coroutine types for UwpScriptStore --- vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp | 4 ++-- vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp index 59bb0c805db..03e88dbb0dc 100644 --- a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp +++ b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "Unicode.h" namespace winrt { @@ -43,7 +42,8 @@ facebook::jsi::ScriptVersion_t UwpScriptStore::getScriptVersion(const std::strin return version; } -std::future UwpScriptStore::getScriptVersionAsync(const std::string &bundleUri) { +winrt::Windows::Foundation::IAsyncOperation +UwpScriptStore::getScriptVersionAsync(const std::string &bundleUri) { co_await winrt::resume_background(); const winrt::hstring fileUrl(Microsoft::Common::Unicode::Utf8ToUtf16("ms-appx:///Bundle/" + bundleUri + ".bundle")); diff --git a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h index 6b77db3f9e7..78b1ff112ae 100644 --- a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h +++ b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include namespace Microsoft::ReactNative { @@ -16,7 +16,8 @@ class UwpScriptStore : public facebook::jsi::ScriptStore { static facebook::jsi::ScriptVersion_t GetFileVersion(const std::wstring &filePath); private: - std::future getScriptVersionAsync(const std::string &bundleUri); + winrt::Windows::Foundation::IAsyncOperation getScriptVersionAsync( + const std::string &bundleUri); }; } // namespace Microsoft::ReactNative From 17f2644b3e3b6f7eb24ee3fa422957a7938e99d5 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 9 Jun 2026 02:29:39 -0700 Subject: [PATCH 18/23] clang format --- vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp | 6 +----- vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp index a7f5427779e..a228d77eb56 100644 --- a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp +++ b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp @@ -167,11 +167,7 @@ TEST_CLASS (DynamicReaderWriterTests) { private: template - void TestScalar( - WriterMethod writerMethod, - ReaderMethod readerMethod, - JSValueType runtimeType, - TWriterValue value) { + void TestScalar(WriterMethod writerMethod, ReaderMethod readerMethod, JSValueType runtimeType, TWriterValue value) { IJSValueWriter writer = Microsoft::Internal::TestController::CreateDynamicWriter(); (writer.*writerMethod)(value); diff --git a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp index 03e88dbb0dc..87e21081a06 100644 --- a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp +++ b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp @@ -42,8 +42,8 @@ facebook::jsi::ScriptVersion_t UwpScriptStore::getScriptVersion(const std::strin return version; } -winrt::Windows::Foundation::IAsyncOperation -UwpScriptStore::getScriptVersionAsync(const std::string &bundleUri) { +winrt::Windows::Foundation::IAsyncOperation UwpScriptStore::getScriptVersionAsync( + const std::string &bundleUri) { co_await winrt::resume_background(); const winrt::hstring fileUrl(Microsoft::Common::Unicode::Utf8ToUtf16("ms-appx:///Bundle/" + bundleUri + ".bundle")); From 8fbb256bde5cf49bcf10684a6f4a0c79d78c9eff Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 9 Jun 2026 18:27:05 -0700 Subject: [PATCH 19/23] Add missing find-dotnet-tools --- packages/@react-native-windows/cli/package.json | 1 + .../find-dotnet-tools/package.json | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/@react-native-windows/cli/package.json b/packages/@react-native-windows/cli/package.json index ea9c4d594d3..b1b33dd0afe 100644 --- a/packages/@react-native-windows/cli/package.json +++ b/packages/@react-native-windows/cli/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@react-native-windows/codegen": "0.84.0-preview.2", + "@react-native-windows/find-dotnet-tools": "0.84.0-preview.1", "@react-native-windows/fs": "0.84.0-preview.1", "@react-native-windows/package-utils": "0.84.0-preview.1", "@react-native-windows/telemetry": "0.84.0-preview.1", diff --git a/packages/@react-native-windows/find-dotnet-tools/package.json b/packages/@react-native-windows/find-dotnet-tools/package.json index 77901e68a1c..8a6a8a4887b 100644 --- a/packages/@react-native-windows/find-dotnet-tools/package.json +++ b/packages/@react-native-windows/find-dotnet-tools/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-windows/find-dotnet-tools", "description": "Helpers to locate .NET-based tools (e.g. pwsh) restored via NuGet or available on PATH.", - "version": "0.0.0-canary.1", + "version": "0.84.0-preview.1", "license": "MIT", "scripts": { "build": "rnw-scripts build", @@ -17,21 +17,21 @@ "directory": "packages/@react-native-windows/find-dotnet-tools" }, "dependencies": { - "@react-native-windows/fs": "^0.0.0-canary.72" + "@react-native-windows/fs": "0.84.0-preview.1" }, "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": "^8.36.0", - "@typescript-eslint/parser": "^8.36.0", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", "eslint": "^8.19.0", "prettier": "^3.6.2", "typescript": "5.0.4" }, "beachball": { - "defaultNpmTag": "canary", + "defaultNpmTag": "preview", "disallowedChangeTypes": [ "major", "minor", From 7f50e37e5de3e124e3af59f37a354e8d1abefac5 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 10 Jun 2026 01:03:43 -0700 Subject: [PATCH 20/23] Increase timeout to 25 min --- .ado/jobs/node-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ado/jobs/node-tests.yml b/.ado/jobs/node-tests.yml index c8772aba334..78a13e317d4 100644 --- a/.ado/jobs/node-tests.yml +++ b/.ado/jobs/node-tests.yml @@ -17,7 +17,7 @@ jobs: - ${{ each nodeVersion in parameters.versions }}: - job: NodeTests${{ nodeVersion }} displayName: Node Tests v${{ nodeVersion }} - timeoutInMinutes: 20 + timeoutInMinutes: 25 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }} From 00e7b83655779c8b9e53462ed4549b9dd0154de0 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 10 Jun 2026 01:06:32 -0700 Subject: [PATCH 21/23] Increase yarn lint timeout to 45 min --- .ado/jobs/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ado/jobs/linting.yml b/.ado/jobs/linting.yml index 399cd890a9b..d5109fe31cd 100644 --- a/.ado/jobs/linting.yml +++ b/.ado/jobs/linting.yml @@ -12,7 +12,7 @@ parameters: jobs: - job: Linting displayName: Linting - timeoutInMinutes: 40 + timeoutInMinutes: 45 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }} steps: From 9b63c6333c86934a5155c09afaff1bb33b0564c1 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 10 Jun 2026 11:33:16 -0700 Subject: [PATCH 22/23] Revert "Increase yarn lint timeout to 45 min" This reverts commit 00e7b83655779c8b9e53462ed4549b9dd0154de0. --- .ado/jobs/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ado/jobs/linting.yml b/.ado/jobs/linting.yml index d5109fe31cd..399cd890a9b 100644 --- a/.ado/jobs/linting.yml +++ b/.ado/jobs/linting.yml @@ -12,7 +12,7 @@ parameters: jobs: - job: Linting displayName: Linting - timeoutInMinutes: 45 + timeoutInMinutes: 40 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }} steps: From e6835862581e86327d82613f0cadc921ef10c659 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 10 Jun 2026 11:33:24 -0700 Subject: [PATCH 23/23] Revert "Increase timeout to 25 min" This reverts commit 7f50e37e5de3e124e3af59f37a354e8d1abefac5. --- .ado/jobs/node-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ado/jobs/node-tests.yml b/.ado/jobs/node-tests.yml index 78a13e317d4..c8772aba334 100644 --- a/.ado/jobs/node-tests.yml +++ b/.ado/jobs/node-tests.yml @@ -17,7 +17,7 @@ jobs: - ${{ each nodeVersion in parameters.versions }}: - job: NodeTests${{ nodeVersion }} displayName: Node Tests v${{ nodeVersion }} - timeoutInMinutes: 25 + timeoutInMinutes: 20 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }}