diff --git a/.ado/jobs/e2e-test.yml b/.ado/jobs/e2e-test.yml index 204e4c82666..9cd5b6a892a 100644 --- a/.ado/jobs/e2e-test.yml +++ b/.ado/jobs/e2e-test.yml @@ -320,7 +320,7 @@ jobs: condition: and(failed(), eq(variables.StartedFabricTests, 'true')) continueOnError: true - - powershell: | + - pwsh: | if (Test-Path "packages/e2e-test-app-fabric/test/__image_snapshots__/__diff_output__") { Write-Host "##vso[task.setvariable variable=DiffOutputExists]true" } 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 ea12ead689d..a962aa661ed 100644 --- a/packages/@react-native-windows/automation/package.json +++ b/packages/@react-native-windows/automation/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@react-native-windows/automation-channel": "0.0.0-canary.1052", + "@react-native-windows/find-dotnet-tools": "0.0.0-canary.1", "@react-native-windows/fs": "^0.0.0-canary.72", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", 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 b66c099b7f5..6d7a0a2846d 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.0.0-canary.133", + "@react-native-windows/find-dotnet-tools": "0.0.0-canary.1", "@react-native-windows/fs": "^0.0.0-canary.72", "@react-native-windows/package-utils": "^0.0.0-canary.98", "@react-native-windows/telemetry": "^0.0.0-canary.133", 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 d8b316a46b2..97da9d6e163 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/vnext/package.json b/vnext/package.json index 77061d9aac2..f008cea4c2a 100644 --- a/vnext/package.json +++ b/vnext/package.json @@ -73,6 +73,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@react-native-windows/codegen": "0.0.0-canary.133", + "@react-native-windows/find-dotnet-tools": "0.0.0-canary.1", "@react-native/metro-config": "0.85.0-nightly-20260114-f15985f4f", "@rnw-scripts/babel-react-native-config": "0.0.0", "@rnw-scripts/eslint-config": "1.2.38", diff --git a/yarn.lock b/yarn.lock index 3930c317407..6ee2ecc7b23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3551,6 +3551,7 @@ __metadata: "@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" @@ -3580,6 +3581,7 @@ __metadata: "@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" @@ -3663,6 +3665,23 @@ __metadata: 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" @@ -15993,6 +16012,7 @@ __metadata: "@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"