diff --git a/.ado/build-template.yml b/.ado/build-template.yml new file mode 100644 index 00000000000..2cfece1bbc0 --- /dev/null +++ b/.ado/build-template.yml @@ -0,0 +1,540 @@ +# +# Shared build and pack template used by CI and PR pipelines. +# CI sets buildEnvironment: Continuous for signed Official builds. +# PR sets buildEnvironment: PullRequest for Unofficial validation builds. +# + +parameters: +- name: buildEnvironment + type: string + default: PullRequest + values: [PullRequest, Continuous] + +- name: isReleaseBuild + type: string + default: auto + values: [auto, 'true', 'false'] + +- name: AgentPool + type: object + default: + Medium: + name: rnw-pool-4-microsoft + demands: ImageOverride -equals rnw-img-vs2022-node22 + Large: + name: rnw-pool-8-microsoft + demands: ImageOverride -equals rnw-img-vs2022-node22 + +- name: desktopBuildMatrix + type: object + default: + - Name: X64Debug + BuildConfiguration: Debug + BuildPlatform: x64 + - Name: X64Release + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86Debug + BuildConfiguration: Debug + BuildPlatform: x86 + - Name: X86Release + BuildConfiguration: Release + BuildPlatform: x86 + - Name: ARM64ECDebug + BuildConfiguration: Debug + BuildPlatform: ARM64EC + - Name: ARM64ECRelease + BuildConfiguration: Release + BuildPlatform: ARM64EC + +- name: universalBuildMatrix + type: object + default: + - Name: X64DebugFabric + BuildConfiguration: Debug + BuildPlatform: x64 + - Name: X64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86DebugFabric + BuildConfiguration: Debug + BuildPlatform: x86 + - Name: X86ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x86 + - Name: Arm64DebugFabric + BuildConfiguration: Debug + BuildPlatform: ARM64 + - Name: Arm64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: ARM64 + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + ${{ else }}: + template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: ${{ parameters.AgentPool.Medium }} + ${{ if ne(parameters.buildEnvironment, 'Continuous') }}: + settings: + skipBuildTagsForGitHubPullRequests: true + featureFlags: + autoEnablePREfastWithNewRuleset: false # PREfast produces 0 actionable findings; auto-enable injects /analyze into every C++ TU, generating ~2656 SARIF files that Guardian uploads for ~19 min per native build + sdl: + credscan: + suppressionsFile: $(Build.SourcesDirectory)\.ado\config\CredScanSuppressions.json + spotBugs: + enabled: false # We don't have any java, but random packages in node_modules do + prefast: + enabled: false + stages: + #================================================================= + # Stage: Setup + # Detects build scenario, bumps versions (developer builds), + # and publishes version variables for downstream stages. + #================================================================= + - stage: Setup + jobs: + - job: Setup + displayName: Setup + variables: [template: .ado/variables/shared.yml@self] + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 15 + steps: + # 1. Detect whether this is a release build or a developer build + - pwsh: | + $param = '${{ parameters.isReleaseBuild }}' + $buildReason = $env:BUILD_REASON + $sourceMessage = $env:SOURCE_MESSAGE + $prSourceBranch = $env:PR_SOURCE_BRANCH + + if ($param -eq 'true') { + $isRelease = $true + Write-Host "Release build forced by parameter" + } + elseif ($param -eq 'false') { + $isRelease = $false + Write-Host "Developer build forced by parameter" + } + else { + # Auto-detect + if ($buildReason -eq 'PullRequest') { + # PR: check source branch + $isRelease = $prSourceBranch.StartsWith('prepare-release/') + Write-Host "PR source branch: $prSourceBranch -> isRelease=$isRelease" + } + else { + # CI/Manual: check commit message + $firstLine = ($sourceMessage -split "`n")[0].Trim() + $isRelease = $firstLine.StartsWith("RELEASE:") + Write-Host "Commit message first line: $firstLine -> isRelease=$isRelease" + } + } + + Write-Host "RESULT: isReleaseBuild=$isRelease" + Write-Host "##vso[task.setvariable variable=isReleaseBuild;isOutput=true]$isRelease" + Write-Host "##vso[task.setvariable variable=isReleaseBuild]$isRelease" + name: detectScenario + displayName: Detect build scenario + env: + BUILD_REASON: $(Build.Reason) + SOURCE_MESSAGE: $(Build.SourceVersionMessage) + PR_SOURCE_BRANCH: $(System.PullRequest.SourceBranch) + + # 2. Full checkout (needed for beachball bump on developer builds; + # harmless ~1 min overhead on release builds where bump is skipped) + - template: .ado/templates/checkout-full.yml@self + parameters: + persistCredentials: true + + # 3. Node.js (always needed — setVersionEnvVars.js requires it) + - task: UseNode@1 + inputs: + version: '24.x' + displayName: Use Node.js 24.x + + - template: .ado/templates/compute-beachball-branch-name.yml@self + + # 4. Beachball tooling (developer builds only) + # Inlined from strict-yarn-install.yml because template references + # do not support the condition: keyword. + - pwsh: | + $packageJson = Get-Content ./package.json | ConvertFrom-Json + $packageJson.scripts = $packageJson.scripts | Select-Object * -ExcludeProperty postinstall + $packageJson | ConvertTo-Json | Out-File ./package.json + displayName: Remove postinstall + condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) + + - script: npx --yes midgard-yarn-strict@1.2.4 @rnw-scripts/beachball-config + displayName: Strict yarn install @rnw-scripts/beachball-config + condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) + + - script: npx lage build --scope @rnw-scripts/prepare-release --scope @rnw-scripts/beachball-config + displayName: Build prepare-release and beachball-config + condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) + + # 5. Beachball check (Developer PR only) + - pwsh: npx beachball check --branch "origin/$env:BEACHBALL_BRANCH" --verbose --changehint "##vso[task.logissue type=error]Run 'yarn change' from root of repo to generate a change file." + displayName: Check for change files + condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False'), eq(variables['Build.Reason'], 'PullRequest')) + env: + BEACHBALL_BRANCH: $(BeachBallBranchName) + + # 6. Bump versions via prepare-release (Developer builds only) + - pwsh: npx prepare-release --bump-only --branch "$env:BEACHBALL_BRANCH" --no-color + displayName: Bump versions (prepare-release --bump-only) + condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) + env: + BEACHBALL_BRANCH: $(BeachBallBranchName) + + # 7. Read version and set pipeline variables (always) + - template: .ado/templates/set-version-vars.yml@self + parameters: + buildEnvironment: Continuous + + # 8. Include npmPack.js for Release pipeline (CI only) + - script: copy ".ado\scripts\npmPack.js" "$(Build.StagingDirectory)\versionEnvVars\npmPack.js" + displayName: Include npmPack.js in VersionEnvVars artifact + condition: and(succeeded(), eq('${{ parameters.buildEnvironment }}', 'Continuous')) + + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Publish version variables' + targetPath: $(Build.StagingDirectory)/versionEnvVars + artifactName: VersionEnvVars + + #================================================================= + # Stage: Build + # Shipping builds (Desktop + Universal), ESRP signing, NPM pack, + # linting, and NuGet packing. + #================================================================= + - stage: Build + dependsOn: Setup + jobs: + # Create NPM packages + - job: RnwNpmPack + displayName: Create NPM packages + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 60 + cancelTimeoutInMinutes: 5 + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/prepare-js-env.yml@self + parameters: + agentImage: HostedImage + + - script: node .ado/scripts/npmPack.js --clean --no-color "$(Pipeline.Workspace)\published-packages" + displayName: Pack npm packages + + - script: dir /s "$(Pipeline.Workspace)\published-packages" + displayName: Show created npm packages + + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Publish npm pack artifacts' + condition: succeededOrFailed() + targetPath: $(Pipeline.Workspace)/published-packages + artifactName: NpmPackedTarballs + + # Run linting + - template: .ado/jobs/linting.yml@self + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + # Build, test, and sign Desktop DLLs + - ${{ each matrix in parameters.desktopBuildMatrix }}: + - job: Desktop${{ matrix.Name }} + displayName: Desktop ${{ matrix.Name }} + pool: ${{ parameters.AgentPool.Large }} + timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout + variables: + - template: .ado/variables/windows.yml@self + # Enable if any issues RNTesterIntegrationTests::* become unstable. + - name: Desktop.IntegrationTests.SkipRNTester + value: false + #5059 - Disable failing or intermittent tests (IntegrationTestHarness,WebSocket,Logging). + #10732 - WebSocketIntegrationTest::SendReceiveSsl fails on Windows Server 2022. + #12714 - Disable for first deployment of test website. + #14217 - Reneable RNTesterIntegrationTests + - name: Desktop.IntegrationTests.Filter + value: > + (FullyQualifiedName!=RNTesterIntegrationTests::IntegrationTestHarness)& + (FullyQualifiedName!=RNTesterIntegrationTests::WebSocket)& + (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketBlob)& + (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketMultipleSend)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendConsecutive)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveLargeMessage)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveSsl)& + (FullyQualifiedName!=Microsoft::React::Test::HttpOriginPolicyIntegrationTest)& + (FullyQualifiedName!=RNTesterIntegrationTests::Dummy)& + (FullyQualifiedName!=RNTesterIntegrationTests::Fetch)& + (FullyQualifiedName!=RNTesterIntegrationTests::XHRSample)& + (FullyQualifiedName!=RNTesterIntegrationTests::Blob)& + (FullyQualifiedName!=RNTesterIntegrationTests::Logging) + #6799 - HostFunctionTest, HostObjectProtoTest crash under JSI/V8; + # PreparedJavaScriptSourceTest asserts/fails under JSI/ChakraCore + - name: Desktop.UnitTests.Filter + value: > + (FullyQualifiedName!~HostFunctionTest)& + (FullyQualifiedName!~HostObjectProtoTest)& + (FullyQualifiedName!~PreparedJavaScriptSourceTest) + steps: + # Build and test + - template: .ado/jobs/desktop-single.yml@self + parameters: + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + buildEnvironment: ${{ parameters.buildEnvironment }} + + # Stage shipping artifacts (copies to staging dir, does not publish) + - template: .ado/templates/publish-build-artifacts.yml@self + parameters: + oneESMode: true + artifactName: Desktop + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + contents: | + React.Windows.Desktop\** + React.Windows.Desktop.DLL\** + React.Windows.Desktop.Test.DLL\** + + # ESRP sign (CI only — service connection unavailable in PR/Unofficial mode) + - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + - template: .ado/templates/esrp-codesign-binaries.yml@self + parameters: + displayName: 'CodeSign Desktop Binaries' + folderPath: $(Build.StagingDirectory)/NuGet/Desktop/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + pattern: | + **/react-native-win32.dll + + templateContext: + sdl: + prefast: + enabled: false + binskim: + analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\React.Windows.Desktop.DLL\react-native-win32.dll' + outputs: + - output: pipelineArtifact + displayName: 'Upload build logs' + condition: succeededOrFailed() + targetPath: $(BuildLogDirectory) + artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload crash dumps' + condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) + targetPath: '$(CrashDumpRootPath)' + artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Publish Artifact: Desktop.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}' + artifactName: Desktop.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} + targetPath: $(Build.StagingDirectory)/NuGet/Desktop/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + + # Build, test, and sign Universal DLLs + - ${{ each matrix in parameters.universalBuildMatrix }}: + - job: Universal${{ matrix.Name }} + displayName: Universal ${{ matrix.Name }} + pool: ${{ parameters.AgentPool.Large }} + timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout + variables: + - template: .ado/variables/windows.yml@self + # Some tasks run on a different user (VssAdministrator) instead of the default user (AzDevOps). + # Keep NuGet cache independent from the user directory. + - name: NUGET_PACKAGES + value: $(Agent.TempDirectory)/NuGetPackages + steps: + # Build and test + - template: .ado/jobs/universal-single.yml@self + parameters: + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + buildEnvironment: ${{ parameters.buildEnvironment }} + + # Stage shipping artifacts (copies to staging dir, does not publish) + - template: .ado/templates/publish-build-artifacts.yml@self + parameters: + oneESMode: true + artifactName: ReactWindows + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + contents: | + Microsoft.ReactNative\Microsoft.ReactNative.* + Microsoft.ReactNative.CsWinRT\Microsoft.ReactNative.Projection.* + + # ESRP sign (CI only — service connection unavailable in PR/Unofficial mode) + - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + - template: .ado/templates/esrp-codesign-binaries.yml@self + parameters: + displayName: 'CodeSign Microsoft.ReactNative Binaries' + folderPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + pattern: | + **/Microsoft.ReactNative.dll + **/Microsoft.ReactNative.winmd + **/Microsoft.ReactNative.Projection.dll + + templateContext: + sdl: + prefast: + enabled: false + binskim: + analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.dll' + outputs: + - output: pipelineArtifact + displayName: 'Upload build logs' + condition: succeededOrFailed() + targetPath: $(BuildLogDirectory) + artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload crash dumps' + condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) + targetPath: '$(CrashDumpRootPath)' + artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Publish Artifact: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}' + artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} + targetPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + + # Create NuGet packages + - job: RNWNuget + displayName: Pack NuGet + dependsOn: + - RnwNpmPack + - Linting + - ${{ each matrix in parameters.desktopBuildMatrix }}: + - Desktop${{ matrix.Name }} + - ${{ each matrix in parameters.universalBuildMatrix }}: + - Universal${{ matrix.Name }} + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 60 # Protect against the long CodeSign task + + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/prepare-js-env.yml@self + + - template: .ado/templates/apply-published-version-vars.yml@self + + # The commit tag in the nuspec requires that we use at least nuget 5.8 (because things break with nuget versions before and VS 16.8 or later) + - task: NuGetToolInstaller@1 + inputs: + versionSpec: ">=5.8.0" + + - template: .ado/templates/prep-and-pack-nuget.yml@self + parameters: + artifactName: ReactWindows + publishCommitId: $(publishCommitId) + npmVersion: $(npmVersion) + packMicrosoftReactNative: true + packMicrosoftReactNativeCxx: true + slices: + - platform: x64 + configuration: Release + - platform: x86 + configuration: Release + - platform: ARM64 + configuration: Release + + - template: .ado/templates/prep-and-pack-nuget.yml@self + parameters: + artifactName: Desktop + publishCommitId: $(publishCommitId) + npmVersion: $(npmVersion) + packDesktop: true + slices: + - platform: x64 + configuration: Release + - platform: x86 + configuration: Release + - platform: ARM64EC + configuration: Release + - platform: x64 + configuration: Debug + - platform: x86 + configuration: Debug + - platform: ARM64EC + configuration: Debug + + # ESRP sign (CI only — service connection unavailable in PR/Unofficial mode) + - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + - template: .ado/templates/esrp-codesign-nuget.yml@self + parameters: + displayName: 'CodeSign all NuGet packages' + folderPath: $(System.DefaultWorkingDirectory)/NugetRootFinal + pattern: '**/*.nupkg' + + # Tag the source commit when a RELEASE build succeeds (CI only) + - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + - pwsh: | + $tag = "react-native-windows_v$(npmVersion)" + Write-Host "Creating tag $tag on $(Build.SourceVersion)" + $authHeader = "AUTHORIZATION: bearer $env:SYSTEM_ACCESSTOKEN" + Write-Host "##vso[task.setsecret]$authHeader" + git -c http.extraheader="$authHeader" tag $tag "$(Build.SourceVersion)" + git -c http.extraheader="$authHeader" push origin $tag + displayName: Tag release sources + condition: and(succeeded(), startsWith(variables['Build.SourceVersionMessage'], 'RELEASE:')) + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + + templateContext: + sdl: + binskim: + analyzeTargetGlob: '$(System.DefaultWorkingDirectory)\NugetRoot\**\Microsoft.ReactNative\Microsoft.ReactNative.dll;$(System.DefaultWorkingDirectory)\NugetRoot\**\React.Windows.Desktop.DLL\react-native-win32.dll' + outputs: + - output: pipelineArtifact + displayName: 'Publish final nuget artifacts' + targetPath: $(System.DefaultWorkingDirectory)\NugetRootFinal + artifactName: "ReactWindows-final-nuget" + + #================================================================= + # Stage: Tests + # Independent test jobs that don't require native build artifacts. + # Runs in parallel with Build stage (both depend only on Setup). + #================================================================= + - stage: Tests + displayName: Tests + dependsOn: Setup + jobs: + - template: .ado/jobs/node-tests.yml@self + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: .ado/jobs/playground.yml@self + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: .ado/jobs/e2e-test.yml@self + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + #================================================================= + # Stage: CLI + # CLI init verification — tests the final NuGet + npm packages. + # Runs after Build stage completes (needs RNWNuget artifacts). + #================================================================= + - stage: CLI + displayName: CLI + dependsOn: Build + jobs: + - template: .ado/jobs/cli-init-windows.yml@self + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + diff --git a/.ado/ci-pipeline.yml b/.ado/ci-pipeline.yml new file mode 100644 index 00000000000..f7b84b8ff3f --- /dev/null +++ b/.ado/ci-pipeline.yml @@ -0,0 +1,29 @@ +# +# The CI pipeline entry point. +# Extends build-template.yml with buildEnvironment: Continuous for signed Official builds. +# + +name: 0.0.$(Date:yyMM.d)$(Rev:rrr) + +trigger: none +pr: none + +parameters: + - name: isReleaseBuild + displayName: 'Treat as Release build (skip beachball, use committed versions)' + type: string + default: auto + values: + - auto + - 'true' + - 'false' + +variables: + - template: variables/windows.yml + - group: RNW Secrets + +extends: + template: build-template.yml@self + parameters: + buildEnvironment: Continuous + isReleaseBuild: ${{ parameters.isReleaseBuild }} diff --git a/.ado/compliance.yml b/.ado/compliance.yml deleted file mode 100644 index 659bb734967..00000000000 --- a/.ado/compliance.yml +++ /dev/null @@ -1,140 +0,0 @@ -name: 0.0.$(Date:yyMM.d)$(Rev:rrr) - -parameters: -- name: AgentPool - type: object - default: - Medium: - name: rnw-pool-4-microsoft - demands: ImageOverride -equals rnw-img-vs2022-node22 - Large: - name: rnw-pool-8-microsoft - demands: ImageOverride -equals rnw-img-vs2022-node22 -- name: forceCodeQL - displayName: Force CodeQL to rebuild databases - type: boolean - default: false -- name: complianceWarnOnly - displayName: Convert compliance errors to warnings - type: boolean - default: true # Let's get all results in this pipeline - -variables: - - template: variables/windows.yml - - group: RNW Secrets - - name: Codeql.Enabled - value: true - - ${{ if eq(parameters.forceCodeQL, true) }}: - - name: Codeql.Cadence - value: 0 - - ${{ if eq(parameters.forceCodeQL, false) }}: - - name: Codeql.Cadence - value: 120 # In hours, default to only run every 5 days - -trigger: none -pr: none - -jobs: - - job: RnwUniversalCompliance - displayName: RNW Universal Compliance - pool: ${{ parameters.AgentPool.Large }} - timeoutInMinutes: 360 # Compliance tasks recommend to 3x usual build timeout - - steps: - - template: templates/checkout-shallow.yml - - - template: templates/prepare-js-env.yml - - - template: templates/set-version-vars.yml - parameters: - buildEnvironment: Continuous - - - template: templates/publish-version-vars.yml - - - template: templates/prepare-build-env.yml - parameters: - platform: x64 - configuration: Release - buildEnvironment: Continuous - - - template: templates/apply-published-version-vars.yml - - # Pre-build compliance tasks - - - template: templates/run-compliance-prebuild.yml - parameters: - complianceWarnOnly: ${{ parameters.complianceWarnOnly }} - - - task: NuGetAuthenticate@1 - - # AgentES Task (https://aka.ms/UES) - # Installs and runs the "Agent ES" tool, which scans the source code for banned file types. - - powershell: | - & nuget.exe install AgentES -FallbackSource https://microsoft.pkgs.visualstudio.com/_packaging/Undocked.Shell.Services/nuget/v3/index.json - $AgentESPath = (Get-ChildItem -Path AgentES* -Filter AgentES.exe -Recurse | %{$_.FullName}) - & $AgentESPath $env:BUILD_SOURCESDIRECTORY -e:$env:BUILD_SOURCESDIRECTORY\.ado\config\AgentES.Exemptions.json -b - displayName: "⚖️ AgentES - Scan of Repository for UES Policy Violations" - workingDirectory: $(Agent.BuildDirectory) - continueOnError: ${{ parameters.complianceWarnOnly }} - - # Initialize CodeQL 3000 Task (https://aka.ms/codeql3000) - # Performs static code analysis. - - task: CodeQL3000Init@0 - displayName: "🛡️ Initialize CodeQL" - continueOnError: ${{ parameters.complianceWarnOnly }} - - # Build RNW - - - template: templates/msbuild-sln.yml - parameters: - solutionDir: vnext - solutionName: Microsoft.ReactNative.NewArch.sln - buildPlatform: x64 - buildConfiguration: Release - - # Post-build compliance tasks - - - template: templates/run-compliance-postbuild.yml - parameters: - complianceWarnOnly: ${{ parameters.complianceWarnOnly }} - - # Attack Surface Analyzer (ASA) for SDL compliance - # This is integrated into the compliance pipeline but runs independently - # Note: ASA requires before/after snapshots, so we run a separate analysis - - task: PowerShell@2 - displayName: '🛡️ Attack Surface Analyzer - Note' - inputs: - targetType: inline - script: | - Write-Host "==========================================" - Write-Host "Attack Surface Analyzer (ASA) Information" - Write-Host "==========================================" - Write-Host "" - Write-Host "ASA runs as a separate job in the PR pipeline (see stages.yml)." - Write-Host "It performs before/after snapshot analysis of the build process." - Write-Host "" - Write-Host "For manual ASA runs or to view results:" - Write-Host "1. Check PR pipeline artifacts for ASA_Results" - Write-Host "2. Review docs/attack-surface-analyzer.md for guidance" - Write-Host "3. Run ASA locally: dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI" - Write-Host "" - Write-Host "✅ ASA integration is active in PR builds" - - # Finalize CodeQL 3000 Task (https://aka.ms/codeql3000) - # Performs static code analysis. - - task: CodeQL3000Finalize@0 - displayName: "🛡️ Finalize CodeQL" - inputs: - # Enable TSA for automatic bug filing from CodeQL - TSAEnabled: true - TSAOptions: | - { - "areaPath": "OS\\Windows Client and Services\\WinPD\\SPICE\\ReactNative", - "iterationPath": "OS\\Future", - "notificationAliases": ["$(TSANotificationAliases)"], - "codebaseAdmins": ["$(TSACodebaseAdmins)"], - "bugTags": ["SDL", "Security"], - "instanceUrl": "https://dev.azure.com/microsoft", - "projectName": "OS" - } - continueOnError: ${{ parameters.complianceWarnOnly }} diff --git a/.ado/continuous.yml b/.ado/continuous.yml deleted file mode 100644 index f20e680ad11..00000000000 --- a/.ado/continuous.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: RNW CI $(Date:yyyyMMdd).$(Rev:r) - -trigger: none # will disable CI builds entirely -pr: none - -variables: - - group: RNW Secrets - - group: platform-override-zero-permission-token - -parameters: - - name: AgentPool - type: object - default: - Small: - name: rnw-pool-2 - demands: ImageOverride -equals rnw-img-vs2022-node22 - Medium: - name: rnw-pool-4 - demands: ImageOverride -equals rnw-img-vs2022-node22 - Large: - name: rnw-pool-8 - demands: ImageOverride -equals rnw-img-vs2022-node22 - -stages: - - template: stages.yml - parameters: - buildEnvironment: Continuous - AgentPool: ${{ parameters.AgentPool }} diff --git a/.ado/integrate-rn.yaml b/.ado/integrate-rn.yaml index 092d0424f76..b9ec5384ada 100644 --- a/.ado/integrate-rn.yaml +++ b/.ado/integrate-rn.yaml @@ -33,7 +33,7 @@ jobs: displayName: yarn integrate-rn continueOnError: true - - powershell: | + - pwsh: | if (!(Test-Path "$(Agent.TempDirectory)\integration-report.md")) { dir "$(Agent.TempDirectory)" throw "No integration report generated" @@ -43,7 +43,7 @@ jobs: } displayName: Test for changes - - powershell: | + - pwsh: | $reportWithHeader = "${{ parameters.commitTitle}}`n`n" + (Get-Content -Raw $(Agent.TempDirectory)\integration-report.md) # Trim to max GitHub PR length $trimmedReport = $reportWithHeader.substring(0, [math]::min(65536, $reportWithHeader.length)) @@ -55,12 +55,12 @@ jobs: git commit -F $(Agent.TempDirectory)\commit-message.md displayName: Commit changes - - powershell: | + - pwsh: | $commitHash = git log --format=%H -n 1 Write-Output "##vso[task.setvariable variable=FirstCommit]$commitHash" displayName: Save base commit - - powershell: | + - pwsh: | $patchScope = node -e " const path = require('path'); const {enumerateRepoPackages} = require('@react-native-windows/package-utils'); @@ -72,7 +72,7 @@ jobs: yarn change --scope @($patchScope.split(',')) --message "${{ parameters.commitTitle}}" --type patch displayName: Create patch changefiles - - powershell: | + - pwsh: | $prereleaseScope = node -e " const path = require('path'); const {enumerateRepoPackages} = require('@react-native-windows/package-utils'); @@ -93,7 +93,7 @@ jobs: - script: git push origin integrate-${{ parameters.reactNativeVersion }} displayName: Push changes - - powershell: | + - pwsh: | $headers = @{'Accept'='application/vnd.github.v3+json'; 'Authorization'='Token $(githubAuthToken)'} $body = '{"head": "integrate-${{ parameters.reactNativeVersion }}", "base": "main", "title": "${{ parameters.commitTitle}}"}' Invoke-WebRequest -Method Post -Headers $headers -Body $body https://api.github.com/repos/microsoft/react-native-windows/pulls diff --git a/.ado/jobs/attack-surface-analyzer.yml b/.ado/jobs/attack-surface-analyzer.yml deleted file mode 100644 index 5b8e7f11a7e..00000000000 --- a/.ado/jobs/attack-surface-analyzer.yml +++ /dev/null @@ -1,406 +0,0 @@ -# Job to run Attack Surface Analyzer (ASA) for SDL compliance -# Validates that installers or high-privilege programs do not weaken OS security -parameters: - - name: buildEnvironment - type: string - default: PullRequest - values: - - PullRequest - - SecurePullRequest - - Continuous - - name: AgentPool - type: object - - name: complianceWarnOnly - displayName: Convert compliance errors to warnings - type: boolean - default: true - -jobs: - # Only run ASA for SecurePullRequest builds - - ${{if eq(parameters.buildEnvironment, 'SecurePullRequest')}}: - - job: AttackSurfaceAnalyzer - displayName: Attack Surface Analyzer (ASA) 🛡️ - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 60 - cancelTimeoutInMinutes: 5 - - variables: - - template: ../variables/windows.yml - - steps: - - template: ../templates/checkout-shallow.yml - - - template: ../templates/prepare-js-env.yml - - - template: ../templates/set-version-vars.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - - - template: ../templates/prepare-build-env.yml - parameters: - platform: x64 - configuration: Release - buildEnvironment: ${{ parameters.buildEnvironment }} - - # Authenticate to NuGet feeds - - task: NuGetAuthenticate@1 - displayName: '🛡️ Authenticate NuGet' - - # Install Attack Surface Analyzer CLI tool - - task: PowerShell@2 - displayName: '🛡️ Install Attack Surface Analyzer' - inputs: - errorActionPreference: 'continue' - targetType: inline - script: | - Write-Host "Installing Attack Surface Analyzer (ASA) CLI tool..." - # Install from public NuGet.org feed - dotnet tool install --global Microsoft.CST.AttackSurfaceAnalyzer.CLI --add-source https://api.nuget.org/v3/index.json - - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to install ASA tool" - exit 1 - } - - # Take "before" snapshot of the system - - task: PowerShell@2 - displayName: '🛡️ ASA - Collect Before Snapshot' - inputs: - targetType: inline - script: | - # Refresh environment PATH to include .NET global tools - $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") - - Write-Host "Taking 'before' snapshot of system state..." - # Collect files (-f), registry (-r), services (-s), ports (-p), certificates (-c) - asa collect -f -r -s -p -c --directories "$(Build.SourcesDirectory)" --verbose - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to collect 'before' snapshot" - exit 1 - } - Write-Host "✅ Before snapshot collected successfully" - - # Build the React Native Windows solution - # This simulates the "installation" that ASA will analyze - - template: ../templates/msbuild-sln.yml - parameters: - solutionDir: vnext - solutionName: Microsoft.ReactNative.NewArch.sln - buildPlatform: x64 - buildConfiguration: Release - - # Optional: Build NuGet packages if needed - # This step simulates package creation which could modify system state - - task: PowerShell@2 - displayName: '🛡️ ASA - Simulate Package Installation' - inputs: - targetType: inline - script: | - Write-Host "Simulating package installation for ASA analysis..." - Write-Host "Build artifacts are in place for analysis" - # Note: Actual NuGet package installation would go here if needed - # For now, we're analyzing the build process itself - - # Take "after" snapshot of the system - - task: PowerShell@2 - displayName: '🛡️ ASA - Collect After Snapshot' - inputs: - targetType: inline - script: | - # Refresh environment PATH to include .NET global tools - $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") - - Write-Host "Taking 'after' snapshot of system state..." - # Collect files (-f), registry (-r), services (-s), ports (-p), certificates (-c) - asa collect -f -r -s -p -c --directories "$(Build.SourcesDirectory)" --verbose - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to collect 'after' snapshot" - exit 1 - } - Write-Host "✅ After snapshot collected successfully" - - # Export comparison results - - task: PowerShell@2 - displayName: '🛡️ ASA - Export Comparison Results' - inputs: - targetType: inline - errorActionPreference: 'continue' - script: | - # Refresh environment PATH to include .NET global tools - $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") - - Write-Host "Comparing before and after snapshots..." - - # Create output directory for results - $resultsDir = "$(Build.ArtifactStagingDirectory)\ASA_Results" - New-Item -ItemType Directory -Force -Path $resultsDir | Out-Null - - Write-Host "Generating comparison results (SARIF and JSON)..." - - # Export as SARIF format for security analysis - $sarifOutput = "$resultsDir\asa-comparison.sarif" - Write-Host "Attempting SARIF export to: $sarifOutput" - try { - asa export-collect --outputsarif "$sarifOutput" --verbose - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ SARIF export successful" - } else { - Write-Warning "SARIF export command returned exit code: $LASTEXITCODE" - } - } catch { - Write-Warning "SARIF export failed with exception: $($_.Exception.Message)" - } - - # Export as JSON format for additional analysis - $jsonOutput = "$resultsDir\asa-comparison.json" - Write-Host "Attempting JSON export to: $jsonOutput" - try { - asa export-collect "$jsonOutput" --verbose - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ JSON export successful" - } else { - Write-Warning "JSON export command returned exit code: $LASTEXITCODE" - } - } catch { - Write-Warning "JSON export failed with exception: $($_.Exception.Message)" - } - - # ENHANCED: Check for actual generated files and copy them if needed - Write-Host "Checking for generated ASA files..." - - # Initialize arrays to collect files - $generatedFiles = @() - - # Check multiple possible locations where ASA might write files - $searchLocations = @( - "$(Build.SourcesDirectory)", # Source directory - (Get-Location).Path, # Current working directory - $env:TEMP, # Temp directory - $env:USERPROFILE # User profile directory - ) - - Write-Host "Searching for ASA output files in multiple locations..." - - foreach ($location in $searchLocations) { - if (Test-Path $location) { - Write-Host "Checking: $location" - - # Look for timestamp-based output files that ASA actually generates - $locationFiles = @() - $locationFiles += Get-ChildItem -Path $location -Filter "*summary*.json*" -ErrorAction SilentlyContinue - $locationFiles += Get-ChildItem -Path $location -Filter "*vs_*.json*" -ErrorAction SilentlyContinue - $locationFiles += Get-ChildItem -Path $location -Filter "*.sarif" -ErrorAction SilentlyContinue - - if ($locationFiles.Count -gt 0) { - Write-Host " Found $($locationFiles.Count) files in $location" - $generatedFiles += $locationFiles - } - } - } - - if ($generatedFiles.Count -gt 0) { - Write-Host "Found $($generatedFiles.Count) ASA output files total:" - foreach ($file in $generatedFiles) { - Write-Host " - $($file.FullName)" - - # Copy to results directory with descriptive names - $destinationName = $file.Name - if ($file.Name -like "*summary*" -or $file.Name -like "*vs_*") { - $destinationName = "asa-comparison-$($file.Name)" - } - - try { - Copy-Item $file.FullName "$resultsDir\$destinationName" -Force - Write-Host " ✅ Copied to: $destinationName" - } catch { - Write-Warning " ❌ Failed to copy $($file.Name): $($_.Exception.Message)" - } - } - } else { - Write-Warning "No ASA output files found in any search locations" - Write-Host "Performing recursive search for ASA-related files..." - - # Try a broader recursive search from current directory - try { - $allFiles = Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | - Where-Object { - $_.Name -like "*asa*" -or - $_.Name -like "*summary*" -or - $_.Name -like "*vs_*" -or - $_.Name -like "*attack*" -or - $_.Extension -eq ".sarif" - } | - Select-Object -First 20 - - if ($allFiles -and $allFiles.Count -gt 0) { - Write-Host "Found potential ASA files through recursive search:" - foreach ($file in $allFiles) { - Write-Host " - $($file.FullName)" - } - } else { - Write-Host "No ASA-related files found through recursive search" - } - } catch { - Write-Warning "Recursive search failed: $($_.Exception.Message)" - } - } - - Write-Host "✅ Export process completed" - Write-Host "Results location: $resultsDir" - - # Ensure we don't exit with error code even if file discovery had issues - exit 0 - - # Analyze results and check for security issues - - task: PowerShell@2 - displayName: '🛡️ ASA - Analyze Results' - inputs: - targetType: inline - script: | - Write-Host "Analyzing ASA results for security regressions..." - - $resultsDir = "$(Build.ArtifactStagingDirectory)\ASA_Results" - - Write-Host "📊 ASA Analysis Summary:" - Write-Host "========================" - - # Look for any ASA result files in the results directory - $allResultFiles = Get-ChildItem -Path $resultsDir -File -ErrorAction SilentlyContinue - - if ($allResultFiles.Count -eq 0) { - Write-Warning "No result files found in $resultsDir" - return - } - - Write-Host "Found $($allResultFiles.Count) result files:" - foreach ($file in $allResultFiles) { - Write-Host " - $($file.Name) ($([math]::Round($file.Length/1KB, 2)) KB)" - } - - # Analyze SARIF files if present - $sarifFiles = $allResultFiles | Where-Object { $_.Extension -eq ".sarif" } - foreach ($sarifFile in $sarifFiles) { - Write-Host "" - Write-Host "📋 SARIF Analysis: $($sarifFile.Name)" - try { - $sarifContent = Get-Content $sarifFile.FullName | ConvertFrom-Json - $findingCount = 0 - - if ($sarifContent.runs -and $sarifContent.runs.Count -gt 0) { - foreach ($run in $sarifContent.runs) { - if ($run.results) { - $findingCount += $run.results.Count - Write-Host " Tool: $($run.tool.driver.name)" - Write-Host " Findings: $($run.results.Count)" - } - } - } - - if ($findingCount -gt 0) { - Write-Host "⚠️ $findingCount security findings detected in SARIF report" - } else { - Write-Host "✅ No security findings in SARIF report" - } - } catch { - Write-Host "⚠️ Could not parse SARIF file: $($_.Exception.Message)" - } - } - - # Analyze JSON files - $jsonFiles = $allResultFiles | Where-Object { $_.Extension -eq ".json" -or $_.Name -like "*.json*" } - foreach ($jsonFile in $jsonFiles) { - Write-Host "" - Write-Host "📋 JSON Analysis: $($jsonFile.Name)" - try { - $content = Get-Content $jsonFile.FullName -Raw - if ($content.Length -gt 0) { - Write-Host " File size: $([math]::Round($jsonFile.Length/1KB, 2)) KB" - Write-Host " Content preview:" - # Show first few lines of content - $lines = $content -split "`n" | Select-Object -First 5 - foreach ($line in $lines) { - if ($line.Trim()) { - Write-Host " $($line.Trim())" - } - } - } - } catch { - Write-Host "⚠️ Could not analyze JSON file: $($_.Exception.Message)" - } - } - - Write-Host "" - Write-Host "🛡️ SDL Compliance Notes:" - Write-Host "- Review all result files for security vulnerabilities" - Write-Host "- Pay attention to privilege escalations" - Write-Host "- Check for unsigned binaries or weak permissions" - Write-Host "- Verify certificate and firewall changes" - Write-Host "" - Write-Host "Results available in build artifacts: ASA_Results" - Write-Host "ASA analysis completed" - continueOnError: ${{ parameters.complianceWarnOnly }} - - # Publish ASA results as build artifact - - task: PublishBuildArtifacts@1 - displayName: '🛡️ Publish ASA Results' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)\ASA_Results' - ArtifactName: 'ASA_Results' - publishLocation: 'Container' - condition: always() - - # Generate summary for PR - - task: PowerShell@2 - displayName: '🛡️ ASA - Generate PR Summary' - inputs: - targetType: inline - script: | - Write-Host "Generating ASA summary for PR..." - - $resultsDir = "$(Build.ArtifactStagingDirectory)\ASA_Results" - - # Check if results directory exists and has files - if (Test-Path $resultsDir) { - $resultFiles = Get-ChildItem -Path $resultsDir -File -ErrorAction SilentlyContinue - - if ($resultFiles.Count -gt 0) { - Write-Host "Found $($resultFiles.Count) ASA result files:" - - # Look for SARIF files to upload as summary - $sarifFiles = $resultFiles | Where-Object { $_.Extension -eq ".sarif" } - foreach ($sarifFile in $sarifFiles) { - Write-Host "Uploading SARIF summary: $($sarifFile.Name)" - Write-Host "##vso[task.uploadsummary]$($sarifFile.FullName)" - } - - # List all result files - foreach ($file in $resultFiles) { - Write-Host " - $($file.Name) ($([math]::Round($file.Length/1KB, 2)) KB)" - } - - Write-Host "✅ ASA results will be available in PR artifacts" - } else { - Write-Host "⚠️ Results directory exists but contains no files" - } - } else { - Write-Host "⚠️ Results directory not found: $resultsDir" - } - - # Additional logging for troubleshooting - Write-Host "" - Write-Host "🔍 Troubleshooting Info:" - Write-Host "Build.SourcesDirectory: $(Build.SourcesDirectory)" - Write-Host "Build.ArtifactStagingDirectory: $(Build.ArtifactStagingDirectory)" - - # Look for any ASA files in the source directory - $sourceFiles = Get-ChildItem -Path "$(Build.SourcesDirectory)" -Filter "*asa*" -ErrorAction SilentlyContinue - $sourceFiles += Get-ChildItem -Path "$(Build.SourcesDirectory)" -Filter "*summary*" -ErrorAction SilentlyContinue - $sourceFiles += Get-ChildItem -Path "$(Build.SourcesDirectory)" -Filter "*vs_*" -ErrorAction SilentlyContinue - - if ($sourceFiles.Count -gt 0) { - Write-Host "ASA files found in source directory:" - foreach ($file in $sourceFiles) { - Write-Host " - $($file.FullName)" - } - } - condition: always() diff --git a/.ado/jobs/cli-init-windows.yml b/.ado/jobs/cli-init-windows.yml index c0d060d9162..c32021c8ab8 100644 --- a/.ado/jobs/cli-init-windows.yml +++ b/.ado/jobs/cli-init-windows.yml @@ -4,13 +4,9 @@ parameters: default: PullRequest values: - PullRequest - - SecurePullRequest - Continuous - name: AgentPool type: object - - name: buildNuGetOnly - type: boolean - default: false - name: buildMatrix type: object default: @@ -21,48 +17,31 @@ parameters: configuration: Release platform: x64 additionalRunArguments: --no-autolink - useNuGet: true - publishNuGet: true - Name: FabricX86Debug template: cpp-app configuration: Debug platform: x86 additionalRunArguments: --no-autolink - useNuGet: true - Name: FabricArm64Release template: cpp-app configuration: Release platform: ARM64 additionalRunArguments: --no-autolink --no-deploy - useNuGet: true - Name: FabricLibX64Release template: cpp-lib configuration: Release platform: x64 - additionalRunArguments: --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: - Name: FabricLibX86Debug template: cpp-lib configuration: Debug platform: x86 - additionalRunArguments: --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: - Name: FabricLibArm64Release template: cpp-lib configuration: Release platform: ARM64 - additionalRunArguments: --no-deploy --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true - - BuildEnvironment: SecurePullRequest - Matrix: - - Name: FabricX64Release - template: cpp-app - configuration: Release - platform: x64 - additionalRunArguments: --no-autolink - useNuGet: true - publishNuGet: true - useExperimentalWinUI3: true + additionalRunArguments: --no-deploy - BuildEnvironment: Continuous Matrix: - Name: FabricX64Debug @@ -70,117 +49,108 @@ parameters: configuration: Debug platform: x64 additionalRunArguments: --no-autolink - useNuGet: true - Name: FabricX64Release template: cpp-app configuration: Release platform: x64 additionalRunArguments: --no-autolink - useNuGet: true - publishNuGet: true - Name: FabricX86Debug template: cpp-app configuration: Debug platform: x86 additionalRunArguments: --no-autolink - useNuGet: true - Name: FabricX86Release template: cpp-app configuration: Release platform: x86 - useNuGet: true - publishNuGet: true - Name: FabricArm64Debug template: cpp-app configuration: Debug platform: ARM64 additionalRunArguments: --no-autolink --no-deploy - useNuGet: true - Name: FabricArm64Release template: cpp-app configuration: Release platform: ARM64 additionalRunArguments: --no-autolink --no-deploy - useNuGet: true - publishNuGet: true - Name: FabricLibX64Debug template: cpp-lib configuration: Debug platform: x64 - additionalRunArguments: --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: - Name: FabricLibX64Release template: cpp-lib configuration: Release platform: x64 - additionalRunArguments: --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: - Name: FabricLibX86Debug template: cpp-lib configuration: Debug platform: x86 - additionalRunArguments: --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: - Name: FabricLibX86Release template: cpp-lib configuration: Release platform: x86 - additionalRunArguments: --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: - Name: FabricLibArm64Debug template: cpp-lib configuration: Debug platform: ARM64 - additionalRunArguments: --no-deploy --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: --no-deploy - Name: FabricLibArm64Release template: cpp-lib configuration: Release platform: ARM64 - additionalRunArguments: --no-deploy --singleproc # --singleproc required to workaround issue #13599 - useNuGet: true + additionalRunArguments: --no-deploy jobs: - ${{ each config in parameters.buildMatrix }}: - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: - ${{ each matrix in config.Matrix }}: - - ${{ if eq(coalesce(matrix.useNuGet, false), parameters.buildNuGetOnly) }}: - - job: CliInitWindows${{ matrix.Name }} - displayName: Verify CliInitWindows ${{ matrix.Name }} + - job: CliInitWindows${{ matrix.Name }} + displayName: Verify CliInitWindows ${{ matrix.Name }} + + # No job-level dependsOn needed — the CLI stage depends on the Build stage, + # which guarantees all build artifacts are available before CLI jobs start. + + variables: [template: ../variables/windows.yml] - ${{ if eq(matrix.useNuGet, true) }}: - dependsOn: - - UniversalBuild${{ matrix.platform }}ReleaseFabric + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 40 + cancelTimeoutInMinutes: 5 - variables: [template: ../variables/windows.yml] + steps: + - template: ../templates/checkout-full.yml + parameters: + persistCredentials: false # We don't need git creds in this job - ${{ if eq(matrix.lowResource, true) }}: - pool: ${{ parameters.AgentPool.Small }} - ${{ else }}: - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 40 - cancelTimeoutInMinutes: 5 + - template: ../templates/prepare-js-env.yml - steps: - - template: ../templates/checkout-full.yml - parameters: - persistCredentials: false # We don't need git creds in this job + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ parameters.platform }} + configuration: ${{ parameters.configuration }} + buildEnvironment: ${{ parameters.buildEnvironment }} - - template: ../templates/prepare-js-env.yml + - template: ../templates/react-native-init-windows.yml + parameters: + template: ${{ matrix.template }} + configuration: ${{ matrix.configuration }} + platform: ${{ matrix.platform }} + additionalInitArguments: ${{ matrix.additionalInitArguments }} + additionalRunArguments: ${{ matrix.additionalRunArguments }} + buildEnvironment: ${{ parameters.buildEnvironment }} + useExperimentalWinUI3: ${{ coalesce(matrix.useExperimentalWinUI3, false) }} - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ parameters.platform }} - configuration: ${{ parameters.configuration }} - buildEnvironment: ${{ parameters.buildEnvironment }} - - - template: ../templates/react-native-init-windows.yml - parameters: - template: ${{ matrix.template }} - configuration: ${{ matrix.configuration }} - platform: ${{ matrix.platform }} - additionalInitArguments: ${{ matrix.additionalInitArguments }} - additionalRunArguments: ${{ matrix.additionalRunArguments }} - buildEnvironment: ${{ parameters.buildEnvironment }} - useNuGet: ${{ coalesce(matrix.useNuGet, false) }} - useExperimentalWinUI3: ${{ coalesce(matrix.useExperimentalWinUI3, false) }} - publishNuGet: ${{ coalesce(matrix.publishNuGet, false) }} + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Upload traces' + condition: succeededOrFailed() + targetPath: $(Build.StagingDirectory)/Tracing + artifactName: Traces - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload Verdaccio.log (on failure)' + condition: failed() + targetPath: verdaccio.log + artifactName: $(Agent.JobName).Verdaccio.log-$(System.JobAttempt) diff --git a/.ado/jobs/desktop-single.yml b/.ado/jobs/desktop-single.yml new file mode 100644 index 00000000000..b9cdcb19fab --- /dev/null +++ b/.ado/jobs/desktop-single.yml @@ -0,0 +1,216 @@ +# +# Step template: Build and test one Desktop platform/config combination. +# Called from build-template.yml which owns the job definition, artifact +# publishing, ESRP signing, and templateContext. +# +# Note: Desktop test filter variables (Desktop.IntegrationTests.Filter, +# Desktop.UnitTests.Filter, Desktop.IntegrationTests.SkipRNTester) must +# be defined at the job level in build-template.yml. +# + +parameters: + - name: buildPlatform + type: string + - name: buildConfiguration + type: string + - name: buildEnvironment + type: string + default: PullRequest + values: + - PullRequest + - Continuous + +steps: + # Set up IIS for integration tests + - pwsh: | + Install-WindowsFeature -Name Web-Server, Web-Scripting-Tools + displayName: Install IIS + + - pwsh: | + Invoke-WebRequest ` + -Uri 'https://download.visualstudio.microsoft.com/download/pr/20598243-c38f-4538-b2aa-af33bc232f80/ea9b2ca232f59a6fdc84b7a31da88464/dotnet-hosting-8.0.3-win.exe' ` + -OutFile dotnet-hosting-8.0.3-win.exe + + Write-Host 'Installing .NET hosting bundle' + Start-Process -Wait -FilePath .\dotnet-hosting-8.0.3-win.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' + Write-Host 'Installed .NET hosting bundle' + + Invoke-WebRequest ` + -Uri 'https://download.visualstudio.microsoft.com/download/pr/f2ec926e-0d98-4a8b-8c70-722ccc2ca0e5/b59941b0c60f16421679baafdb7e9338/dotnet-sdk-7.0.407-win-x64.exe' ` + -OutFile dotnet-sdk-7.0.407-win-x64.exe + + Write-Host 'Installing .NET 7 SDK' + Start-Process -Wait -FilePath .\dotnet-sdk-7.0.407-win-x64.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' + Write-Host 'Installed .NET 7 SDK' + displayName: Install the .NET Core Hosting Bundle + + - template: ../templates/checkout-shallow.yml + + - template: ../templates/prepare-js-env.yml + + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ parameters.buildPlatform }} + configuration: ${{ parameters.buildConfiguration }} + buildEnvironment: ${{ parameters.buildEnvironment }} + + - template: ../templates/apply-published-version-vars.yml + + - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: + - pwsh: | + $newValue = '(FullyQualifiedName!~RNTesterIntegrationTests::)&' + "$(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 + solutionName: ReactWindows-Desktop.sln + buildPlatform: ${{ parameters.buildPlatform }} + buildConfiguration: ${{ parameters.buildConfiguration }} + oneESMode: true + msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props + + - ${{ if and(eq(parameters.buildConfiguration, 'Debug'), eq(parameters.buildPlatform, 'x64')) }}: + - script: yarn bundle + displayName: Build react-native-win32 RNTester bundle + workingDirectory: packages/@office-iss/react-native-win32 + + - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + - template: ../templates/component-governance.yml + + # --- Tests --- + - template: ../templates/discover-google-test-adapter.yml + + - ${{ if ne(parameters.buildPlatform, 'ARM64EC') }}: + - task: VSTest@2 + displayName: Run Desktop Unit Tests + timeoutInMinutes: 5 # Set smaller timeout, due to hangs + inputs: + testSelector: testAssemblies + testAssemblyVer2: | + React.Windows.Desktop.UnitTests/React.Windows.Desktop.UnitTests.dll + # Bug #8000: Tracks fixing the tests + # ReactCommon.UnitTests/ReactCommon.UnitTests.exe + pathtoCustomTestAdapters: $(GoogleTestAdapterPath) + searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ parameters.buildPlatform }}/${{ parameters.buildConfiguration }} + testFiltercriteria: $(Desktop.UnitTests.Filter) + runTestsInIsolation: true + platform: ${{ parameters.buildPlatform }} + configuration: ${{ parameters.buildConfiguration }} + publishRunAttachments: true + collectDumpOn: onAbortOnly + vsTestVersion: latest + failOnMinTestsNotRun: true + + # Suspected debug assert in TestRunner hanging tests randomly. Run only on Release for now. + - ${{ if and(eq(parameters.buildConfiguration, 'Release'), ne(variables['Desktop.IntegrationTests.SkipRNTester'], true), ne(parameters.buildPlatform, 'ARM64EC')) }}: + - task: PowerShell@2 + displayName: Set up test servers + inputs: + targetType: filePath + filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tfs\Start-TestServers.ps1 + arguments: -SourcesDirectory $(Build.SourcesDirectory)\vnext -Preload -SleepSeconds 120 + pwsh: true + + - task: DotNetCoreCLI@2 + displayName: Publish Test Website + inputs: + command: publish + publishWebProjects: false + zipAfterPublish: false + projects: $(Build.SourcesDirectory)\vnext\TestWebsite\Microsoft.ReactNative.Test.Website.csproj + arguments: --configuration ${{ parameters.buildConfiguration }} + + - pwsh: | + # Create and make available to IIS + $cert = New-SelfSignedCertificate ` + -Type SSLServerAuthentication ` + -KeyExportPolicy Exportable ` + -Subject 'CN=localhost' ` + -NotAfter ([DateTime]::Now).AddHours(2) ` + -CertStoreLocation Cert:\LocalMachine\My\ + + $certPath = "${env:TEMP}\localhost.pfx" + $certPass = -join ('a'..'z' | Get-Random -Count 32) | ConvertTo-SecureString -AsPlainText -Force + $certHash = $cert.Thumbprint + Write-Host "##vso[task.setvariable variable=TestWebsiteCertificateThumbprint]$certHash" + + # Export PFX + $cert | Export-PfxCertificate -FilePath $certPath -Password $certPass + + # Trust globally + Import-PfxCertificate ` + -Exportable ` + -FilePath $certPath ` + -Password $certPass ` + -CertStoreLocation Cert:\LocalMachine\Root\ + displayName: Install SSL Certificate + + - task: IISWebAppManagementOnMachineGroup@0 + displayName: Create Test Website + inputs: + EnableIIS: false + IISDeploymentType: IISWebsite + ActionIISWebsite: CreateOrUpdateWebsite + SSLCertThumbPrint: $(TestWebsiteCertificateThumbprint) + WebsiteName: RNW Test Website + # Hard-coding x64 for publish path. + # Our MSBuild customizations cause dotnet commands to assume the default PlatformTarget + WebsitePhysicalPath: $(Build.SourcesDirectory)\vnext\target\x64\${{ parameters.buildConfiguration }}\Microsoft.ReactNative.Test.Website\Publish + WebsitePhysicalPathAuth: WebsiteUserPassThrough + CreateOrUpdateAppPoolForWebsite: false + ConfigureAuthenticationForWebsite: false + AppPoolNameForWebsite: DefaultAppPool + AddBinding: true + Bindings: | + { + bindings: [ + { + "protocol": "http", + "ipAddress": "*", + "port": "5555", + "sslThumbprint": "", + "sniFlag": false + }, + { + "protocol": "https", + "ipAddress": "*", + "port": "5543", + "sslThumbprint": "$(TestWebsiteCertificateThumbprint)", + "sniFlag": false + } + ] + } + + - task: PowerShell@2 + displayName: Ensure servers readiness + inputs: + targetType: 'inline' + pwsh: true + script: | + # Test website + Invoke-WebRequest -UseBasicParsing -Uri 'http://localhost:5555' + Invoke-WebRequest -UseBasicParsing -Uri 'https://localhost:5543' + + # Bundler + Invoke-WebRequest -UseBasicParsing -Uri "http://localhost:8081/IntegrationTests/IntegrationTestsApp.bundle?platform=windows&dev=true" + continueOnError: true + + - task: VSTest@2 + displayName: Run Desktop Integration Tests + inputs: + testSelector: testAssemblies + testAssemblyVer2: React.Windows.Desktop.IntegrationTests\React.Windows.Desktop.IntegrationTests.dll + searchFolder: $(Build.SourcesDirectory)\vnext\target\${{ parameters.buildPlatform }}\${{ parameters.buildConfiguration }} + testFiltercriteria: $(Desktop.IntegrationTests.Filter) + runTestsInIsolation: true + platform: ${{ parameters.buildPlatform }} + configuration: ${{ parameters.buildConfiguration }} + publishRunAttachments: true + collectDumpOn: onAbortOnly + vsTestVersion: latest + failOnMinTestsNotRun: true + otherConsoleOptions: '/blame -- RunConfiguration.TestSessionTimeout=300000' + + - template: ../templates/stop-packagers.yml diff --git a/.ado/jobs/desktop.yml b/.ado/jobs/desktop.yml deleted file mode 100644 index b37b00ed158..00000000000 --- a/.ado/jobs/desktop.yml +++ /dev/null @@ -1,348 +0,0 @@ -parameters: - - name: buildEnvironment - type: string - default : PullRequest - values: - - PullRequest - - SecurePullRequest - - Continuous - - - name: AgentPool - type: object - - - name: buildMatrix - type: object - default: - - BuildEnvironment: PullRequest - Matrix: - - Name: X64Debug - BuildConfiguration: Debug - BuildPlatform: x64 - - Name: X64Release - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86Debug - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: ARM64ECDebug - BuildConfiguration: Debug - BuildPlatform: ARM64EC - - Name: ARM64ECRelease - BuildConfiguration: Release - BuildPlatform: ARM64EC - - BuildEnvironment: SecurePullRequest - Matrix: - - Name: X64Debug - BuildConfiguration: Debug - BuildPlatform: x64 - UseExperimentalWinUI3: true - - BuildEnvironment: Continuous - Matrix: - - Name: X64Debug - BuildConfiguration: Debug - BuildPlatform: x64 - - Name: X64Release - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86Debug - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: X86Release - BuildConfiguration: Release - BuildPlatform: x86 - - Name: ARM64ECDebug - BuildConfiguration: Debug - BuildPlatform: ARM64EC - - Name: ARM64ECRelease - BuildConfiguration: Release - BuildPlatform: ARM64EC - -jobs: - - ${{ each config in parameters.buildMatrix }}: - - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: - - ${{ each matrix in config.Matrix }}: - - job: Desktop${{ matrix.Name }} - displayName: Desktop ${{ matrix.Name }} - - variables: - - template: ../variables/windows.yml - - # Enable if any issues RNTesterIntegrationTests::* become unstable. - - name: Desktop.IntegrationTests.SkipRNTester - value: false - - #5059 - Disable failing or intermittent tests (IntegrationTestHarness,WebSocket,Logging). - #10732 - WebSocketIntegrationTest::SendReceiveSsl fails on Windows Server 2022. - #12714 - Disable for first deployment of test website. - # RNTesterIntegrationTests::WebSocket - # RNTesterIntegrationTests::WebSocketBlob - # RNTesterIntegrationTests::WebSocketMultipleSend - #14217 - Reneable RNTesterIntegrationTests - # RNTesterIntegrationTests::Dummy - # RNTesterIntegrationTests::Fetch - # RNTesterIntegrationTests::XHRSample - # RNTesterIntegrationTests::Blob - # RNTesterIntegrationTests::Logging - # - CI agents show the following server-side errors (local runs succeed): - # - [0x801901f4] Internal server error (500). - # - [0x800710dd] The operation identifier is not valid. - # WebSocketIntegrationTest::ConnectClose)& - # WebSocketIntegrationTest::ConnectNoClose)& - # WebSocketIntegrationTest::SendReceiveClose)& - # WebSocketIntegrationTest::SendConsecutive)& - # WebSocketIntegrationTest::SendReceiveLargeMessage)& - # WebSocketIntegrationTest::SendReceiveSsl)& - - name: Desktop.IntegrationTests.Filter - value: > - (FullyQualifiedName!=RNTesterIntegrationTests::IntegrationTestHarness)& - (FullyQualifiedName!=RNTesterIntegrationTests::WebSocket)& - (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketBlob)& - (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketMultipleSend)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendConsecutive)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveLargeMessage)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveSsl)& - (FullyQualifiedName!=Microsoft::React::Test::HttpOriginPolicyIntegrationTest)& - (FullyQualifiedName!=RNTesterIntegrationTests::Dummy)& - (FullyQualifiedName!=RNTesterIntegrationTests::Fetch)& - (FullyQualifiedName!=RNTesterIntegrationTests::XHRSample)& - (FullyQualifiedName!=RNTesterIntegrationTests::Blob)& - (FullyQualifiedName!=RNTesterIntegrationTests::Logging) - #6799 - - # HostFunctionTest - Crashes under JSI/V8 - # HostObjectProtoTest - Crashes under JSI/V8 - # PreparedJavaScriptSourceTest - Asserts/Fails under JSI/ChakraCore - - name: Desktop.UnitTests.Filter - value: > - (FullyQualifiedName!~HostFunctionTest)& - (FullyQualifiedName!~HostObjectProtoTest)& - (FullyQualifiedName!~PreparedJavaScriptSourceTest) - - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 80 # how long to run the job before automatically cancelling - Issue 13442 - cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them - - steps: - # Set up IIS tests { - - pwsh: | - Install-WindowsFeature -Name Web-Server, Web-Scripting-Tools - displayName: Install IIS - - - pwsh: | - Invoke-WebRequest ` - -Uri 'https://download.visualstudio.microsoft.com/download/pr/20598243-c38f-4538-b2aa-af33bc232f80/ea9b2ca232f59a6fdc84b7a31da88464/dotnet-hosting-8.0.3-win.exe' ` - -OutFile dotnet-hosting-8.0.3-win.exe - - Write-Host 'Installing .NET hosting bundle' - Start-Process -Wait -FilePath .\dotnet-hosting-8.0.3-win.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' - Write-Host 'Installed .NET hosting bundle' - - Invoke-WebRequest ` - -Uri 'https://download.visualstudio.microsoft.com/download/pr/f2ec926e-0d98-4a8b-8c70-722ccc2ca0e5/b59941b0c60f16421679baafdb7e9338/dotnet-sdk-7.0.407-win-x64.exe' ` - -OutFile dotnet-sdk-7.0.407-win-x64.exe - - Write-Host 'Installing .NET 7 SDK' - Start-Process -Wait -FilePath .\dotnet-sdk-7.0.407-win-x64.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' - Write-Host 'Installed .NET 7 SDK' - displayName: Install the .NET Core Hosting Bundle - - # } Set up IIS tests - - - template: ../templates/checkout-shallow.yml - - - template: ../templates/prepare-js-env.yml - - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - buildEnvironment: ${{ config.BuildEnvironment }} - - - template: ../templates/apply-published-version-vars.yml - - - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: - - powershell: | - $newValue = '(FullyQualifiedName!~RNTesterIntegrationTests::)&' + "$(Desktop.IntegrationTests.Filter)" - Write-Host "##vso[task.setvariable variable=Desktop.IntegrationTests.Filter]$newValue" - displayName: Update Desktop.IntegrationTests.Filter to exclude RNTester integration tests - - - ${{ if eq(matrix.UseExperimentalWinUI3, true) }}: - - template: ../templates/enable-experimental-winui3.yml - parameters: - workingDir: vnext - - - template: ../templates/msbuild-sln.yml - parameters: - solutionDir: vnext - solutionName: ReactWindows-Desktop.sln - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - - - ${{ if and(eq(matrix.BuildConfiguration, 'Debug'), eq(matrix.BuildPlatform, 'x64')) }}: - - script: yarn bundle - displayName: Build react-native-win32 RNTester bundle - workingDirectory: packages/@office-iss/react-native-win32 - - - ${{ if eq(config.BuildEnvironment, 'Continuous') }}: - - template: ../templates/component-governance.yml - - - template: ../templates/discover-google-test-adapter.yml - - - ${{ if ne(matrix.BuildPlatform, 'ARM64EC') }}: - - task: VSTest@2 - displayName: Run Desktop Unit Tests - timeoutInMinutes: 5 # Set smaller timeout , due to hangs - inputs: - testSelector: testAssemblies - testAssemblyVer2: | - React.Windows.Desktop.UnitTests/React.Windows.Desktop.UnitTests.dll - # Bug #8000: Tracks fixing the tests - # ReactCommon.UnitTests/ReactCommon.UnitTests.exe - pathtoCustomTestAdapters: $(GoogleTestAdapterPath) - searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - testFiltercriteria: $(Desktop.UnitTests.Filter) - runTestsInIsolation: true - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - publishRunAttachments: true - collectDumpOn: onAbortOnly - vsTestVersion: latest - failOnMinTestsNotRun: true - - # Suspected debug assert in TestRunner hanging tests randomly. Run only on Release for now. - - ${{ if and(eq(matrix.BuildConfiguration, 'Release'), ne(variables['Desktop.IntegrationTests.SkipRNTester'], true), ne(matrix.BuildPlatform, 'ARM64EC')) }}: - - task: PowerShell@2 - displayName: Set up test servers - inputs: - targetType: filePath # filePath | inline - filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tfs\Start-TestServers.ps1 - arguments: -SourcesDirectory $(Build.SourcesDirectory)\vnext -Preload -SleepSeconds 120 - - - task: DotNetCoreCLI@2 - displayName: Publish Test Website - inputs: - command: publish - publishWebProjects: false - zipAfterPublish: false - projects: $(Build.SourcesDirectory)\vnext\TestWebsite\Microsoft.ReactNative.Test.Website.csproj - arguments: --configuration ${{ matrix.BuildConfiguration }} - - - pwsh: | - # Create and make available to IIS - $cert = New-SelfSignedCertificate ` - -Type SSLServerAuthentication ` - -KeyExportPolicy Exportable ` - -Subject 'CN=localhost' ` - -NotAfter ([DateTime]::Now).AddHours(2) ` - -CertStoreLocation Cert:\LocalMachine\My\ - - $certPath = "${env:TEMP}\localhost.pfx" - $certPass = -join ('a'..'z' | Get-Random -Count 32) | ConvertTo-SecureString -AsPlainText -Force - $certHash = $cert.Thumbprint - Write-Host "##vso[task.setvariable variable=TestWebsiteCertificateThumbprint]$certHash" - - # Export PFX - $cert | Export-PfxCertificate -FilePath $certPath -Password $certPass - - # Trust globally - Import-PfxCertificate ` - -Exportable ` - -FilePath $certPath ` - -Password $certPass ` - -CertStoreLocation Cert:\LocalMachine\Root\ - displayName: Install SSL Certificate - - - task: IISWebAppManagementOnMachineGroup@0 - displayName: Create Test Website - inputs: - EnableIIS: false - IISDeploymentType: IISWebsite - ActionIISWebsite: CreateOrUpdateWebsite - SSLCertThumbPrint: $(TestWebsiteCertificateThumbprint) - # IIS Website - WebsiteName: RNW Test Website - # Hard-coding x64 for publish path. - # Our MSBuild customizations cause dotnet commans to assume the default PlatformTarget - WebsitePhysicalPath: $(Build.SourcesDirectory)\vnext\target\x64\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative.Test.Website\Publish - WebsitePhysicalPathAuth: WebsiteUserPassThrough - CreateOrUpdateAppPoolForWebsite: false - ConfigureAuthenticationForWebsite: false - # IIS Application pool - AppPoolNameForWebsite: DefaultAppPool - # IIS Bindings - # See https://stackoverflow.com/questions/60089756 - AddBinding: true - Bindings: | - { - bindings: [ - { - "protocol": "http", - "ipAddress": "*", - "port": "5555", - "sslThumbprint": "", - "sniFlag": false - }, - { - "protocol": "https", - "ipAddress": "*", - "port": "5543", - "sslThumbprint": "$(TestWebsiteCertificateThumbprint)", - "sniFlag": false - } - ] - } - - - task: PowerShell@2 - displayName: Ensure servers readiness - inputs: - targetType: 'inline' - script: | - # Test website - Invoke-WebRequest -Uri 'http://localhost:5555' - Invoke-WebRequest -Uri 'https://localhost:5543' - - # Bundler - Invoke-WebRequest -UseBasicParsing -Uri "http://localhost:8081/IntegrationTests/IntegrationTestsApp.bundle?platform=windows&dev=true" - continueOnError: true - - - task: VSTest@2 - displayName: Run Desktop Integration Tests - inputs: - testSelector: testAssemblies - testAssemblyVer2: React.Windows.Desktop.IntegrationTests\React.Windows.Desktop.IntegrationTests.dll - searchFolder: $(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }} - testFiltercriteria: $(Desktop.IntegrationTests.Filter) - runTestsInIsolation: true - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - publishRunAttachments: true - collectDumpOn: onAbortOnly - vsTestVersion: latest - failOnMinTestsNotRun: true - otherConsoleOptions: '/blame -- RunConfiguration.TestSessionTimeout=300000' - - - template: ../templates/stop-packagers.yml - - - task: BinSkim@4 - displayName: Run Binskim Analysis - condition: eq('${{ matrix.BuildConfiguration }}', 'Release') - inputs: - InputType: 'Basic' - Function: 'analyze' - TargetPattern: 'guardianGlob' - AnalyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\React.Windows.Desktop.DLL\react-native-win32.dll' - AnalyzeVerbose: true - toolVersion: 'Latest' - continueOnError: true - - - template: ../templates/publish-build-artifacts.yml - parameters: - artifactName: Desktop - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - contents: | - React.Windows.Desktop\** - React.Windows.Desktop.DLL\** - React.Windows.Desktop.Test.DLL\** diff --git a/.ado/jobs/e2e-test.yml b/.ado/jobs/e2e-test.yml index 6ea13a358fc..244f8911c44 100644 --- a/.ado/jobs/e2e-test.yml +++ b/.ado/jobs/e2e-test.yml @@ -5,7 +5,6 @@ parameters: default : PullRequest values: - PullRequest - - SecurePullRequest - Continuous - name: AgentPool type: object @@ -46,7 +45,7 @@ jobs: configuration: Release buildEnvironment: ${{ config.buildEnvironment }} - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=BuildLogDirectory]$(Build.BinariesDirectory)\${{ matrix.BuildPlatform }}\BuildLogs" displayName: Set BuildLogDirectory @@ -55,6 +54,7 @@ jobs: inputs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Start-Tracing.ps1 + pwsh: true - template: ../templates/run-windows-with-certificates.yml parameters: @@ -91,15 +91,10 @@ jobs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Stop-Tracing.ps1 arguments: -NoAnalysis -outputFolder $(Build.StagingDirectory)/Tracing + pwsh: true condition: true - - task: PublishBuildArtifacts@1 - displayName: Upload traces - inputs: - pathtoPublish: '$(Build.StagingDirectory)/Tracing' - artifactName: 'Traces - $(Agent.JobName)-$(System.JobAttempt)' - condition: true - + # Publish tasks moved to templateContext.outputs (1ES compliance) - task: CopyFiles@2 displayName: Copy Fabric snapshots inputs: @@ -116,20 +111,35 @@ jobs: contents: "**" condition: failed() - - task: PublishPipelineArtifact@1 - displayName: "Publish Artifact: RNTesterApp Fabric" - inputs: - artifactName: RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) - targetPath: $(Build.StagingDirectory)/RNTesterApp-Fabric - condition: failed() - - - task: PublishPipelineArtifact@1 - displayName: "Publish Artifact: Fabric Snapshots" - inputs: - artifactName: Snapshots - RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) - targetPath: $(Build.StagingDirectory)/snapshots-fabric - condition: failed() - - template: ../templates/upload-build-logs.yml parameters: + oneESMode: true buildLogDirectory: '$(BuildLogDirectory)' + + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Upload traces' + condition: true + targetPath: $(Build.StagingDirectory)/Tracing + artifactName: Traces - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Publish Artifact: RNTesterApp Fabric' + condition: failed() + targetPath: $(Build.StagingDirectory)/RNTesterApp-Fabric + artifactName: RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Publish Artifact: Fabric Snapshots' + condition: failed() + targetPath: $(Build.StagingDirectory)/snapshots-fabric + artifactName: Snapshots - RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload build logs' + condition: succeededOrFailed() + targetPath: $(BuildLogDirectory) + artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload crash dumps' + condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) + targetPath: '$(CrashDumpRootPath)' + artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) diff --git a/.ado/jobs/linting.yml b/.ado/jobs/linting.yml index 612a0fe6738..7e4c85d8d67 100644 --- a/.ado/jobs/linting.yml +++ b/.ado/jobs/linting.yml @@ -4,7 +4,6 @@ parameters: default : PullRequest values: - PullRequest - - SecurePullRequest - Continuous - name: AgentPool @@ -21,8 +20,6 @@ jobs: - template: ../templates/prepare-js-env.yml - - template: ../templates/run-compliance-prebuild.yml - - script: yarn format:verify displayName: yarn format:verify diff --git a/.ado/jobs/node-tests.yml b/.ado/jobs/node-tests.yml index ef1167cefa2..8824f5504ba 100644 --- a/.ado/jobs/node-tests.yml +++ b/.ado/jobs/node-tests.yml @@ -4,7 +4,6 @@ parameters: default : PullRequest values: - PullRequest - - SecurePullRequest - Continuous - name: AgentPool diff --git a/.ado/jobs/nuget-desktop.yml b/.ado/jobs/nuget-desktop.yml deleted file mode 100644 index e93822c4bfd..00000000000 --- a/.ado/jobs/nuget-desktop.yml +++ /dev/null @@ -1,44 +0,0 @@ -parameters: - - name: buildEnvironment - type: string - default : PullRequest - values: - - PullRequest - - SecurePullRequest - - Continuous - - name: AgentPool - type: object - -jobs: - - ${{if ne(parameters.buildEnvironment, 'SecurePullRequest')}}: - - job: PackDesktop - displayName: Pack Desktop NuGet Package - variables: [template: ../variables/windows.yml] - dependsOn: - - DesktopX64Release - - DesktopX86Debug - - DesktopARM64ECRelease - - pool: ${{ parameters.AgentPool.Small }} - timeoutInMinutes: 30 # how long to run the job before automatically cancelling - cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them - steps: - - template: ../templates/checkout-shallow.yml - - - template: ../templates/yarn-install.yml - - - task: NuGetToolInstaller@1 - inputs: - versionSpec: ">=5.8.0" - - - template: ../templates/prep-and-pack-nuget.yml - parameters: - artifactName: Desktop - packDesktop: true - slices: - - platform: x64 - configuration: Release - - platform: x86 - configuration: Debug - - platform: ARM64EC - configuration: Release diff --git a/.ado/jobs/playground.yml b/.ado/jobs/playground.yml index 7da2ba11f6f..188288f3366 100644 --- a/.ado/jobs/playground.yml +++ b/.ado/jobs/playground.yml @@ -4,7 +4,6 @@ parameters: default : PullRequest values: - PullRequest - - SecurePullRequest - Continuous - name: AgentPool type: object @@ -28,23 +27,6 @@ parameters: BuildConfiguration: Release BuildPlatform: x64 SolutionFile: Playground-Composition.sln - - BuildEnvironment: SecurePullRequest - Matrix: - - Name: X86DebugCompositionExperimentalWinUI3 - BuildConfiguration: Debug - BuildPlatform: x86 - SolutionFile: Playground-Composition.sln - UseExperimentalWinUI3: true - - Name: X64DebugCompositionExperimentalWinUI3 - BuildConfiguration: Debug - BuildPlatform: x64 - SolutionFile: Playground-Composition.sln - UseExperimentalWinUI3: true - - Name: X64ReleaseCompositionExperimentalWinUI3 - BuildConfiguration: Release - BuildPlatform: x64 - SolutionFile: Playground-Composition.sln - UseExperimentalWinUI3: true - BuildEnvironment: Continuous Matrix: - Name: X86DebugComposition @@ -117,19 +99,19 @@ jobs: # Execute debug feature tests (skip this step for the Win32 Playground app and for release builds) # Need to re-setup ProcDump a 2nd time for this to work correctly - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=CrashDumpRootPath]$(Build.StagingDirectory)\DebugTestCrashDumps" New-Item -Path '$(Build.StagingDirectory)\DebugTestCrashDumps' -ItemType Directory displayName: Set CrashDumpRootPath - - powershell: | + - pwsh: | & $(Build.SourcesDirectory)\.ado\scripts\RunProcDump.ps1 -ProcDumpArgs @("-mm", "-i", "$(CrashDumpRootPath)") -ProcDumpInstallPath "$(ProcDumpPath)" -Verbose displayName: Setup ProcDump as AeDebug # This step loose-file-deploys the UWP Playground app and uses it as an RNW host for a series # of debug feature tests. In the future, these tests should be performed against a host app # better suited for automated tests (probably the E2E test app). - - powershell: | + - pwsh: | $appRecipeToolPath = Join-Path (& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) "Common7\IDE\DeployAppRecipe.exe" if (!(Test-Path $appRecipeToolPath)) { throw "can't find '$appRecipeToolPath'" } $platDirs = @{ "x64" = "x64\"; "x86" = ""} # map from ADO BuildPlatform arg to VS build output dir part @@ -143,7 +125,7 @@ jobs: displayName: Run Debug Feature Tests timeoutInMinutes: 5 - - powershell: | + - pwsh: | foreach ($logFile in (ls "$(Build.StagingDirectory)\DebugTestLogs\*.log")) { Write-Host "logFile: '$logFile'" Get-Content $logFile | ForEach-Object {Write-Host "##[debug]$_"} @@ -153,16 +135,30 @@ jobs: - template: ../templates/upload-build-logs.yml parameters: + oneESMode: true artifactName: 'DebugTest $(Agent.JobName)-$(System.JobAttempt)' buildLogDirectory: '$(Build.StagingDirectory)\DebugTestLogs' - ${{if eq(config.BuildEnvironment, 'Continuous')}}: - template: ../templates/cleanup-certificate.yml + # App Package publish moved to templateContext.outputs (1ES compliance) + + templateContext: + outputs: - ${{ if eq(matrix.UploadAppx, true) }}: - - task: PublishBuildArtifacts@1 - displayName: Upload App Package - inputs: - pathtoPublish: 'packages/playground/windows/AppPackages/playground' - artifactName: 'Playground-${{ matrix.Name }}-$(System.JobAttempt)' + - output: pipelineArtifact + displayName: 'Upload App Package' condition: succeededOrFailed() + targetPath: packages/playground/windows/AppPackages/playground + artifactName: Playground-${{ matrix.Name }}-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload build logs' + condition: succeededOrFailed() + targetPath: $(BuildLogDirectory) + artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload crash dumps' + condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) + targetPath: '$(CrashDumpRootPath)' + artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) diff --git a/.ado/jobs/setup.yml b/.ado/jobs/setup.yml deleted file mode 100644 index 71ecc18c655..00000000000 --- a/.ado/jobs/setup.yml +++ /dev/null @@ -1,55 +0,0 @@ -parameters: - - name: buildEnvironment - type: string - values: - - PullRequest - - SecurePullRequest - - Continuous - -jobs: - - job: Setup - displayName: Setup - timeoutInMinutes: 4 # Kill the job early to catch Beachball hangs - variables: [template: ../variables/shared.yml] - pool: {vmImage: ubuntu-latest} - - steps: - - task: UseNode@1 - inputs: - version: '22.14.0' - displayName: 'Use Node.js 22.14.0' - - - template: ../templates/checkout-full.yml - parameters: - persistCredentials: true # Git creds needed for beachball - - - powershell: gci env:/BUILD_* - displayName: Show build information - - - template: ../templates/compute-beachball-branch-name.yml - - - template: ../templates/strict-yarn-install.yml - parameters: - workspace: '@rnw-scripts/beachball-config' - - - script: npx lage build --scope @rnw-scripts/beachball-config --no-deps - displayName: Build @rnw-scripts/beachball-config - - - pwsh: | - npx beachball check --verbose 2>&1 | Tee-Object -Variable beachballOutput - $beachballErrors = $beachballOutput | Where-Object { $_ -match "ERROR: *"} - $beachballErrors | ForEach { Write-Host "##vso[task.logissue type=warning]POSSIBLE $_" } - displayName: Warn for possible invalid change files - - - ${{ if endsWith(parameters.buildEnvironment, 'PullRequest') }}: - - script: npx beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run \"yarn change\" from root of repo to generate a change file." - displayName: Check for change files - - - script: npx beachball bump --branch origin/$(BeachBallBranchName) --yes --verbose - displayName: beachball bump - - - template: ../templates/set-version-vars.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - - - template: ../templates/publish-version-vars.yml diff --git a/.ado/jobs/universal-single.yml b/.ado/jobs/universal-single.yml new file mode 100644 index 00000000000..556f932a424 --- /dev/null +++ b/.ado/jobs/universal-single.yml @@ -0,0 +1,71 @@ +# +# Step template: Build and test one Universal platform/config combination. +# Called from build-template.yml which owns the job definition, artifact +# publishing, ESRP signing, and templateContext. +# + +parameters: + - name: buildPlatform + type: string + - name: buildConfiguration + type: string + - name: buildEnvironment + type: string + default: PullRequest + values: + - PullRequest + - Continuous +steps: + - template: ../templates/checkout-shallow.yml + + - template: ../templates/prepare-js-env.yml + + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ parameters.buildPlatform }} + configuration: ${{ parameters.buildConfiguration }} + buildEnvironment: ${{ parameters.buildEnvironment }} + + - template: ../templates/apply-published-version-vars.yml + + - template: ../templates/msbuild-sln.yml + parameters: + solutionDir: vnext + solutionName: Microsoft.ReactNative.NewArch.sln + buildPlatform: ${{ parameters.buildPlatform }} + buildConfiguration: ${{ parameters.buildConfiguration }} + oneESMode: true + msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props + + - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: + - template: ../templates/component-governance.yml + + - task: PowerShell@2 + displayName: Make AnyCPU Reference Assemblies + inputs: + filePath: vnext/Scripts/Tfs/Make-AnyCPU-RefAssemblies.ps1 + arguments: -TargetRoot $(Build.SourcesDirectory)\vnext\target -BuildRoot $(Build.SourcesDirectory)\vnext\target + pwsh: true + + # --- Tests (run against the just-built binaries, no download/restore needed) --- + - template: ../templates/discover-google-test-adapter.yml + + - task: VSTest@2 + displayName: Run Universal Unit Tests (Native) + timeoutInMinutes: 5 # Set smaller timeout, due to hangs + inputs: + testSelector: testAssemblies + testAssemblyVer2: | + Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.exe + Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.exe + Mso.UnitTests/Mso.UnitTests.exe + pathtoCustomTestAdapters: $(GoogleTestAdapterPath) + searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ parameters.buildPlatform }}/${{ parameters.buildConfiguration }} + runTestsInIsolation: true + platform: ${{ parameters.buildPlatform }} + configuration: ${{ parameters.buildConfiguration }} + publishRunAttachments: true + collectDumpOn: onAbortOnly + vsTestVersion: latest + failOnMinTestsNotRun: true + condition: and(succeeded(), not(eq('${{ parameters.buildPlatform }}', 'ARM64'))) diff --git a/.ado/jobs/universal.yml b/.ado/jobs/universal.yml deleted file mode 100644 index 4d7897e4d1d..00000000000 --- a/.ado/jobs/universal.yml +++ /dev/null @@ -1,224 +0,0 @@ - parameters: - - name: buildEnvironment - type: string - default : PullRequest - values: - - PullRequest - - SecurePullRequest - - Continuous - - name: AgentPool - type: object - - name: buildMatrix - type: object - default: - - BuildEnvironment: PullRequest - Matrix: - - Name: X64DebugFabric - BuildConfiguration: Debug - BuildPlatform: x64 - CreateApiDocs: true - - Name: X64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86ReleaseFabric # Specifically built so binskim / tests get run on fabric - BuildConfiguration: Release - BuildPlatform: x86 - - Name: Arm64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: ARM64 - - BuildEnvironment: SecurePullRequest - Matrix: - - Name: X64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x64 - UseExperimentalWinUI3: true - - Name: X86ReleaseFabric # Specifically built so binskim / tests get run on fabric - BuildConfiguration: Release - BuildPlatform: x86 - UseExperimentalWinUI3: true - - BuildEnvironment: Continuous - Matrix: - - Name: X64DebugFabric - BuildConfiguration: Debug - BuildPlatform: x64 - CreateApiDocs: true - - Name: X64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86DebugFabric - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: X86ReleaseFabric # Specifically built so binskim / tests get run on fabric - BuildConfiguration: Release - BuildPlatform: x86 - - Name: Arm64DebugFabric - BuildConfiguration: Debug - BuildPlatform: ARM64 - - Name: Arm64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: ARM64 - - jobs: - - ${{ each config in parameters.buildMatrix }}: - - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: - - ${{ each matrix in config.Matrix }}: - - job: UniversalBuild${{ matrix.Name }} - variables: - - template: ../variables/windows.yml - # Some tasks run on a different user (VssAdministrator) instead of the default user (AzDevOps). - # Keep NuGet cache independent from the user directory. - - name: NUGET_PACKAGES - value: $(Agent.TempDirectory)/NuGetPackages - displayName: Universal Build ${{ matrix.Name }} - pool: ${{ parameters.AgentPool.Large }} - timeoutInMinutes: 60 - cancelTimeoutInMinutes: 5 - - steps: - - template: ../templates/checkout-shallow.yml - - - template: ../templates/prepare-js-env.yml - - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - buildEnvironment: ${{ config.BuildEnvironment }} - - - template: ../templates/apply-published-version-vars.yml - - - ${{ if eq(matrix.UseExperimentalWinUI3, true) }}: - - template: ../templates/enable-experimental-winui3.yml - parameters: - workingDir: vnext - - - template: ../templates/msbuild-sln.yml - parameters: - solutionDir: vnext - solutionName: Microsoft.ReactNative.NewArch.sln - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - - - ${{ if eq(config.BuildEnvironment, 'Continuous') }}: - - template: ../templates/component-governance.yml - - - ${{ if eq(matrix.CreateApiDocs, true) }}: - - powershell: | - $winmd2md_url = "https://github.com/asklar/winmd2md/releases/download/v0.1.16/winmd2md.exe" - Invoke-WebRequest -UseBasicParsing $winmd2md_url -OutFile $env:TEMP\winmd2md.exe - & $env:TEMP\winmd2md.exe /experimental /outputDirectory vnext\target\winmd2md vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.winmd - displayName: "Generate WinRT API docs" - continueOnError: true - - - task: PublishBuildArtifacts@1 - displayName: Upload WinRT API docs - inputs: - pathtoPublish: 'vnext\target\winmd2md' - artifactName: 'WinRT API docs - $(Agent.JobName)-$(System.JobAttempt)' - - - task: PowerShell@2 - displayName: Make AnyCPU Reference Assemblies - inputs: - filePath: vnext/Scripts/Tfs/Make-AnyCPU-RefAssemblies.ps1 - arguments: -TargetRoot $(Build.SourcesDirectory)\vnext\target -BuildRoot $(Build.SourcesDirectory)\vnext\target - - - template: ../templates/publish-build-artifacts.yml - parameters: - artifactName: ReactWindows - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - contents: | - Microsoft.ReactNative\** - Microsoft.ReactNative.Cxx.UnitTests\** - Microsoft.ReactNative.IntegrationTests\** - Microsoft.ReactNative.CsWinRT\** - Microsoft.ReactNative.Managed\** - Microsoft.ReactNative.Managed.CodeGen\** - Microsoft.ReactNative.Managed.CodeGen.UnitTests\** - Microsoft.ReactNative.Managed.UnitTests\** - Mso.UnitTests\** - - - job: UniversalTest${{ matrix.Name }} - variables: - - template: ../variables/windows.yml - # Some tasks run on a different user (VssAdministrator) instead of the default user (AzDevOps). - # Keep NuGet cache independent from the user directory. - - name: NUGET_PACKAGES - value: $(Agent.TempDirectory)/NuGetPackages - displayName: Universal Test ${{ matrix.Name }} - dependsOn: - - UniversalBuild${{ matrix.Name }} - - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 60 - cancelTimeoutInMinutes: 5 - - steps: - - template: ../templates/checkout-shallow.yml - - - template: ../templates/prepare-js-env.yml - - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - buildEnvironment: ${{ config.BuildEnvironment }} - - - task: PowerShell@2 - displayName: Check if this environment meets the development dependencies - inputs: - targetType: filePath - filePath: $(Build.SourcesDirectory)\vnext\Scripts\rnw-dependencies.ps1 - arguments: -NoPrompt -Tags buildLab - - - task: DownloadPipelineArtifact@1 - displayName: Download "ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}" - inputs: - targetPath: $(Build.SourcesDirectory)/vnext/target/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} - - - task: BinSkim@4 - displayName: Run Binskim Analysis - condition: and(succeeded(), eq('${{ matrix.BuildConfiguration }}', 'Release'), ne('${{ matrix.BuildPlatform }}', 'ARM64')) - inputs: - InputType: 'Basic' - Function: 'analyze' - TargetPattern: 'guardianGlob' - AnalyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.dll' - AnalyzeVerbose: true - toolVersion: 'LatestPreRelease' - - - template: ../templates/discover-google-test-adapter.yml - - - task: MSBuild@1 - displayName: Restore NuGet packages - # Should be kept in sync with UniversalBuild - template: ../templates/msbuild-sln.yml - inputs: - solution: vnext/Microsoft.ReactNative.NewArch.sln - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - msbuildArchitecture: x64 - msbuildArguments: - /restore - /p:RestoreLockedMode=true - /p:RestoreForceEvaluate=true - - - task: VSTest@2 - displayName: Run Universal Unit Tests (Native) - timeoutInMinutes: 5 # Set smaller timeout , due to hangs - inputs: - testSelector: testAssemblies - testAssemblyVer2: | - Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.exe - Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.exe - Mso.UnitTests/Mso.UnitTests.exe - pathtoCustomTestAdapters: $(GoogleTestAdapterPath) - searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - runTestsInIsolation: true - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - publishRunAttachments: true - collectDumpOn: onAbortOnly - vsTestVersion: latest - failOnMinTestsNotRun: true - condition: and(succeeded(), not(eq('${{ matrix.BuildPlatform }}', 'ARM64'))) diff --git a/.ado/pr-pipeline.yml b/.ado/pr-pipeline.yml new file mode 100644 index 00000000000..dc744e0b64b --- /dev/null +++ b/.ado/pr-pipeline.yml @@ -0,0 +1,28 @@ +# +# The PR pipeline entry point. +# Extends build-template.yml with buildEnvironment: PullRequest for Unofficial validation builds. +# + +name: $(Date:yyyyMMdd).$(Rev:r) + +trigger: none + +pr: + - main + - master + - "*-stable" + +variables: + - group: platform-override-zero-permission-token + +extends: + template: build-template.yml@self + parameters: + buildEnvironment: PullRequest + AgentPool: + Medium: + name: rnw-pool-4 + demands: ImageOverride -equals rnw-img-vs2022-node22 + Large: + name: rnw-pool-8 + demands: ImageOverride -equals rnw-img-vs2022-node22 diff --git a/.ado/prepare-release-bot.yml b/.ado/prepare-release-bot.yml new file mode 100644 index 00000000000..8ec0586c128 --- /dev/null +++ b/.ado/prepare-release-bot.yml @@ -0,0 +1,82 @@ +name: $(Date:yyyyMMdd).$(Rev:r) + +# Triggers are configured in the ADO pipeline UI: +# - CI triggers on pushes to main and *-stable branches +# - Scheduled triggers for daily runs +# - Manual runs with optional branch override +trigger: none +pr: none + +parameters: + - name: targetBranch + displayName: Target branch for version bump (use default to use pipeline source branch) + type: string + default: (source branch) + values: + - (source branch) + - main + - 0.82-stable + - 0.81-stable + - 0.80-stable + - 0.74-stable + +jobs: + - job: PrepareRelease + displayName: Prepare Release Bot + pool: + vmImage: windows-latest + timeoutInMinutes: 30 + + steps: + - checkout: self + persistCredentials: true + fetchDepth: 1 + fetchTags: false + + - script: | + git config user.name "React-Native-Windows Bot" + git config user.email "53619745+rnbot@users.noreply.github.com" + displayName: Configure Git Identity + + # Extract OAuth token from persistCredentials for GitHub API access (gh CLI) + - pwsh: | + $headerLine = git config --get-regexp "http.*\.extraheader" 2>$null | Select-Object -First 1 + if (-not $headerLine) { + Write-Host "##[error]No HTTP extraheader found. persistCredentials may not be working." + exit 1 + } + $encoded = ($headerLine.Split(' ')[-1]).Trim() + $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded)) + $token = $decoded.Split(':')[-1] + Write-Host "Extracted GitHub OAuth token (length=$($token.Length))" + Write-Host "##vso[task.setvariable variable=GitHubOAuthToken;issecret=true]$token" + displayName: Extract GitHub OAuth token + + - task: NodeTool@0 + displayName: Set Node Version + inputs: + versionSpec: '24.x' + + - script: if not exist %APPDATA%\npm (mkdir %APPDATA%\npm) + displayName: Ensure npm directory for npx commands + + - script: npx --yes midgard-yarn@1.23.34 --ignore-scripts --frozen-lockfile + displayName: yarn install + + - script: npx lage build --scope @rnw-scripts/prepare-release --scope @rnw-scripts/beachball-config + displayName: Build prepare-release and dependencies + + - ${{ if ne(parameters.targetBranch, '(source branch)') }}: + - pwsh: Write-Host "##vso[task.setvariable variable=TargetBranch]${{ parameters.targetBranch }}" + displayName: Set target branch from parameter + - ${{ else }}: + - pwsh: Write-Host "##vso[task.setvariable variable=TargetBranch]$env:SOURCE_BRANCH_NAME" + displayName: Set target branch from source + env: + SOURCE_BRANCH_NAME: $(Build.SourceBranchName) + + - pwsh: npx prepare-release --branch "$env:TARGET_BRANCH" --no-color + displayName: Prepare Release + env: + GH_TOKEN: $(GitHubOAuthToken) + TARGET_BRANCH: $(TargetBranch) diff --git a/.ado/publish.yml b/.ado/publish.yml index 32ce9524823..c78eb7e3bf1 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -1,457 +1,31 @@ -name: 0.0.$(Date:yyMM.d)$(Rev:rrr) +# +# The Publish pipeline entry point. +# This file is a copy of ci-pipeline.yml kept for backward compatibility +# with *-stable branches that haven't been updated yet. +# Once all branches use ci-pipeline.yml, this file can be deleted. +# -parameters: -- name: skipNpmPublish - displayName: Skip Npm Publish - type: boolean - default: false -- name: skipGitPush - displayName: Skip Git Push - type: boolean - default: false -- name: stopOnNoCI - displayName: Stop if latest commit is ***NO_CI*** - type: boolean - default: true -- name: performBeachballCheck - displayName: Perform Beachball Check (Disable when promoting) - type: boolean - default: true -- name: AgentPool - type: object - default: - Medium: - name: rnw-pool-4-microsoft - demands: ImageOverride -equals rnw-img-vs2022-node22 - Large: - name: rnw-pool-8-microsoft - demands: ImageOverride -equals rnw-img-vs2022-node22 +name: 0.0.$(Date:yyMM.d)$(Rev:rrr) -- name: desktopBuildMatrix - type: object - default: - - Name: X64Debug - BuildConfiguration: Debug - BuildPlatform: x64 - - Name: X64Release - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86Debug - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: X86Release - BuildConfiguration: Release - BuildPlatform: x86 - - Name: ARM64ECDebug - BuildConfiguration: Debug - BuildPlatform: ARM64EC - - Name: ARM64ECRelease - BuildConfiguration: Release - BuildPlatform: ARM64EC +trigger: none +pr: none -- name: universalBuildMatrix - type: object - default: - - Name: X64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x86 - - Name: Arm64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: ARM64 - - Name: X64DebugFabric - BuildConfiguration: Debug - BuildPlatform: x64 - - Name: X86DebugFabric - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: Arm64DebugFabric - BuildConfiguration: Debug - BuildPlatform: ARM64 +parameters: + - name: isReleaseBuild + displayName: 'Treat as Release build (skip beachball, use committed versions)' + type: string + default: auto + values: + - auto + - 'true' + - 'false' variables: - template: variables/windows.yml - group: RNW Secrets - - name: SkipGitPushPublishArgs - value: '' - - name: FailCGOnAlert - value: false - - name: EnableCodesign - value: false - - name: ArtifactServices.Symbol.AccountName - value: microsoft - - name: ArtifactServices.Symbol.PAT - value: $(pat-symbols-publish-microsoft) - - name: SourceBranchWithFolders - value: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] - -trigger: none -pr: none -resources: - repositories: - - repository: 1ESPipelineTemplates - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release extends: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + template: build-template.yml@self parameters: - pool: ${{ parameters.AgentPool.Medium }} - customBuildTags: - - ES365AIMigrationTooling - sdl: - credscan: - suppressionsFile: $(Build.SourcesDirectory)\.ado\config\CredScanSuppressions.json - spotBugs: - enabled: false # We don't have any java, but random packages in node_modules do - stages: - - stage: RNWPublish - jobs: - - job: RnwPublishPrep - displayName: React-Native-Windows Publish Prep - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 120 - cancelTimeoutInMinutes: 5 - steps: - - powershell: | - Write-Host "Stopping because commit message contains ***NO_CI***." - $uri = "https://dev.azure.com/microsoft/ReactNative/_apis/build/builds/$(Build.BuildId)?api-version=5.1" - $json = @{status="Cancelling"} | ConvertTo-Json -Compress - $build = Invoke-RestMethod -Uri $uri -Method Patch -Headers @{Authorization = "Bearer $(System.AccessToken)"} -ContentType "application/json" -Body $json - Write-Host $build - Write-Host "Waiting 60 seconds for build cancellation..." - Start-Sleep -Seconds 60 - displayName: Stop pipeline if latest commit message contains ***NO_CI*** - condition: and(${{ parameters.stopOnNoCI }}, contains(variables['Build.SourceVersionMessage'], '***NO_CI***')) - - - template: .ado/templates/checkout-full.yml@self - parameters: - persistCredentials: false # We're going to use rnbot's git creds to publish - - - powershell: gci env:/BUILD_* - displayName: Show build information - - - template: .ado/templates/prepare-js-env.yml@self - - - template: .ado/templates/run-compliance-prebuild.yml@self - - - script: if not exist %USERPROFILE%\AppData\Roaming\npm (mkdir %USERPROFILE%\AppData\Roaming\npm) - displayName: Fix missing npm config - - - pwsh: | - npx beachball check --verbose 2>&1 | Tee-Object -Variable beachballOutput - $beachballErrors = $beachballOutput | Where-Object { $_ -match "ERROR: *"} - $beachballErrors | ForEach { Write-Host "##vso[task.logissue type=error]$_" } - if ( $beachballErrors.Count -gt 0) { throw "Beachball check found $($beachballErrors.Count) errors." } - displayName: Beachball Check - condition: ${{ parameters.performBeachballCheck }} - - - job: RnwNpmPublish - displayName: React-Native-Windows Npm Build Rev Publish - dependsOn: RnwPublishPrep - pool: - name: Azure-Pipelines-1ESPT-ExDShared - image: windows-latest - os: windows - timeoutInMinutes: 120 - cancelTimeoutInMinutes: 5 - steps: - - template: .ado/templates/prepare-js-env.yml@self - parameters: - agentImage: HostedImage - - - template: .ado/templates/configure-git.yml@self - - - pwsh: | - Write-Host "##vso[task.setvariable variable=SkipGitPushPublishArgs]--no-push" - displayName: Enable No-Publish (git) - condition: ${{ parameters.skipGitPush }} - - # Beachball publishes NPM packages to the "$(Pipeline.Workspace)\published-packages" folder. - # It pushes NPM version updates to Git depending on the SkipGitPushPublishArgs variable derived from the skipGitPush parameter. - - script: | - if exist "$(Pipeline.Workspace)\published-packages" rd /s /q "$(Pipeline.Workspace)\published-packages" - mkdir "$(Pipeline.Workspace)\published-packages" - npx beachball publish --no-publish $(SkipGitPushPublishArgs) --pack-to-path "$(Pipeline.Workspace)\published-packages" --branch origin/$(SourceBranchWithFolders) -yes --bump-deps --verbose --access public --message "applying package updates ***NO_CI***" - displayName: Beachball Publish - - - script: dir /s "$(Pipeline.Workspace)\published-packages" - displayName: Show created npm packages - - # Beachball reverts to local state after publish, but we want the updates it added - - script: git pull origin $(SourceBranchWithFolders) - displayName: git pull - - - script: npx @rnw-scripts/create-github-releases --yes --authToken $(githubAuthToken) - displayName: Create GitHub Releases (New Canary Version) - condition: and(succeeded(), ${{ not(parameters.skipGitPush) }}, ${{ eq(variables['Build.SourceBranchName'], 'main') }} ) - - - script: npx --yes @rnw-scripts/create-github-releases@latest --yes --authToken $(githubAuthToken) - displayName: Create GitHub Releases (New Stable Version) - condition: and(succeeded(), ${{ not(parameters.skipGitPush) }}, ${{ ne(variables['Build.SourceBranchName'], 'main') }} ) - - - template: .ado/templates/set-version-vars.yml@self - parameters: - buildEnvironment: Continuous - - - script: echo NpmDistTag is $(NpmDistTag) - displayName: Show NPM dist tag - - - script: dir /s "$(Pipeline.Workspace)\published-packages" - displayName: Show npm packages before ESRP release - - - task: 'SFP.release-tasks.custom-build-release-task.EsrpRelease@10' - displayName: 'ESRP Release to npmjs.com' - condition: and(succeeded(), ne(variables['NpmDistTag'], '')) - inputs: - connectedservicename: 'ESRP-CodeSigning-OGX-JSHost-RNW' - usemanagedidentity: false - keyvaultname: 'OGX-JSHost-KV' - authcertname: 'OGX-JSHost-Auth4' - signcertname: 'OGX-JSHost-Sign3' - clientid: '0a35e01f-eadf-420a-a2bf-def002ba898d' - domaintenantid: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - contenttype: npm - folderlocation: '$(Pipeline.Workspace)\published-packages' - productstate: '$(NpmDistTag)' - owners: 'vmorozov@microsoft.com' - approvers: 'khosany@microsoft.com' - - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 - displayName: 📒 Generate Manifest Npm - inputs: - BuildDropPath: $(System.DefaultWorkingDirectory) - - templateContext: - outputs: - - output: pipelineArtifact - displayName: 'Publish npm pack artifacts' - condition: succeededOrFailed() - targetPath: $(Pipeline.Workspace)/published-packages - artifactName: NpmPackedTarballs - - output: pipelineArtifact - displayName: "📒 Publish Manifest Npm" - artifactName: SBom-$(System.JobAttempt) - targetPath: $(System.DefaultWorkingDirectory)/_manifest - sbomEnabled: false # This output is in fact an SBOM itself - - output: pipelineArtifact - displayName: 'Publish version variables' - targetPath: $(Build.StagingDirectory)/versionEnvVars - artifactName: VersionEnvVars - - - ${{ each matrix in parameters.desktopBuildMatrix }}: - - job: RnwNativeBuildDesktop${{ matrix.Name }} - displayName: Build Desktop ${{ matrix.Name }} - dependsOn: RnwNpmPublish - pool: ${{ parameters.AgentPool.Large }} - timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout - steps: - - template: .ado/templates/prepare-js-env.yml@self - - - template: .ado/templates/prepare-build-env.yml@self - parameters: - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - buildEnvironment: Publish - - - template: .ado/templates/apply-published-version-vars.yml@self - - - template: .ado/templates/msbuild-sln.yml@self - parameters: - solutionDir: vnext - solutionName: ReactWindows-Desktop.sln - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - oneESMode: true ## Files are only copied to staging, not published - - - template: .ado/templates/publish-build-artifacts.yml@self - parameters: - oneESMode: true ## Files are only copied to staging, not published - artifactName: Desktop - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - contents: | - React.Windows.Desktop\** - React.Windows.Desktop.DLL\** - React.Windows.Desktop.Test.DLL\** - - - template: .ado/templates/component-governance.yml@self - - templateContext: - sdl: - binskim: - analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\React.Windows.Desktop.DLL\react-native-win32.dll' - outputs: - - output: pipelineArtifact - displayName: 'Upload build logs' - condition: succeededOrFailed() - targetPath: $(BuildLogDirectory) - artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload crash dumps' - condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) - targetPath: '$(CrashDumpRootPath)' - artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Publish Artifact: Desktop.${{matrix.buildPlatform}}.${{matrix.buildConfiguration}}' - artifactName: Desktop.${{matrix.buildPlatform}}.${{matrix.buildConfiguration}} - targetPath: $(Build.StagingDirectory)/NuGet/Desktop/${{matrix.buildPlatform}}/${{matrix.buildConfiguration}} - - - ${{ each matrix in parameters.universalBuildMatrix }}: - - job: RnwNativeBuildUniversal${{ matrix.Name }} - displayName: Build Universal ${{ matrix.Name }} - dependsOn: RnwNpmPublish - pool: ${{ parameters.AgentPool.Large }} - timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout - steps: - - template: .ado/templates/prepare-js-env.yml@self - - - template: .ado/templates/prepare-build-env.yml@self - parameters: - platform: ${{ matrix.BuildPlatform }} - configuration: ${{ matrix.BuildConfiguration }} - buildEnvironment: Publish - - - template: .ado/templates/apply-published-version-vars.yml@self - - - template: .ado/templates/msbuild-sln.yml@self - parameters: - solutionDir: vnext - solutionName: Microsoft.ReactNative.NewArch.sln - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - oneESMode: true ## Files are only copied to staging, not published - - - task: PowerShell@2 - displayName: Make AnyCPU Reference Assemblies - inputs: - filePath: vnext/Scripts/Tfs/Make-AnyCPU-RefAssemblies.ps1 - arguments: -TargetRoot $(Build.SourcesDirectory)\vnext\target -BuildRoot $(Build.SourcesDirectory)\vnext\target - - - template: .ado/templates/publish-build-artifacts.yml@self - parameters: - oneESMode: true ## Files are only copied to staging, not published - artifactName: ReactWindows - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - contents: | - Microsoft.ReactNative\** - Microsoft.ReactNative.CsWinRT\** - - - template: .ado/templates/component-governance.yml@self - - # Make symbols available through http://symweb. - - task: PublishSymbols@2 - displayName: Publish symbols - inputs: - SearchPattern: vnext/target/**/*.pdb - SymbolServerType: TeamServices - - templateContext: - sdl: - binskim: - analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.dll' - outputs: - - output: pipelineArtifact - displayName: 'Upload build logs' - condition: succeededOrFailed() - targetPath: $(BuildLogDirectory) - artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload crash dumps' - condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) - targetPath: '$(CrashDumpRootPath)' - artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Publish Artifact: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}' - artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} - targetPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - - - job: RNWNuget - dependsOn: - - RnwNpmPublish - - ${{ each matrix in parameters.desktopBuildMatrix }}: - - RnwNativeBuildDesktop${{ matrix.Name }} - - ${{ each matrix in parameters.universalBuildMatrix }}: - - RnwNativeBuildUniversal${{ matrix.Name }} - displayName: Sign Binaries and Publish NuGet - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 120 # Protect against the long CodeSign task - - steps: - - template: .ado/templates/checkout-shallow.yml@self - - - template: .ado/templates/prepare-js-env.yml@self - - - template: .ado/templates/apply-published-version-vars.yml@self - - # The commit tag in the nuspec requires that we use at least nuget 5.8 (because things break with nuget versions before and Vs 16.8 or later) - - task: NuGetToolInstaller@1 - inputs: - versionSpec: ">=5.8.0" - - - template: .ado/templates/prep-and-pack-nuget.yml@self - parameters: - artifactName: ReactWindows - publishCommitId: $(publishCommitId) - npmVersion: $(npmVersion) - packMicrosoftReactNative: true - packMicrosoftReactNativeCxx: true - ${{ if or(eq(variables['EnableCodesign'], 'true'), endsWith(variables['Build.SourceBranchName'], '-stable')) }}: # Sign if EnableCodeSign or on *-stable release builds - signMicrosoft: true - slices: - - platform: x64 - configuration: Release - - platform: x86 - configuration: Release - - platform: ARM64 - configuration: Release - - - template: .ado/templates/prep-and-pack-nuget.yml@self - parameters: - artifactName: Desktop - publishCommitId: $(publishCommitId) - npmVersion: $(npmVersion) - packDesktop: true - ${{ if or(eq(variables['EnableCodesign'], 'true'), endsWith(variables['Build.SourceBranchName'], '-stable')) }}: # Sign if EnableCodeSign or on *-stable release builds - signMicrosoft: true - slices: - - platform: x64 - configuration: Release - - platform: x86 - configuration: Release - - platform: ARM64EC - configuration: Release - - platform: x64 - configuration: Debug - - platform: x86 - configuration: Debug - - platform: ARM64EC - configuration: Debug - - # Symbol Publishing for Work Item 59264834 - MSRC Compliance - - task: PublishSymbols@2 - displayName: 'Publish Symbols to Microsoft Symbol Server' - condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI')) - inputs: - SymbolsFolder: '$(System.DefaultWorkingDirectory)\NugetRoot' - SearchPattern: '**/*.pdb' - SymbolServerType: 'TeamServices' - SymbolsProduct: 'ReactNativeWindows' - SymbolsVersion: '$(Build.BuildNumber)' - SymbolsArtifactName: 'ReactNativeWindows-Symbols' - DetailedLog: true - TreatNotIndexedAsWarning: false - - templateContext: - sdl: - binskim: - analyzeTargetGlob: '$(System.DefaultWorkingDirectory)\NugetRoot\**\Microsoft.ReactNative\Microsoft.ReactNative.dll;$(System.DefaultWorkingDirectory)\NugetRoot\**\React.Windows.Desktop.DLL\react-native-win32.dll' - outputs: - - output: pipelineArtifact - displayName: 'Publish final nuget artifacts' - targetPath: $(System.DefaultWorkingDirectory)\NugetRootFinal - artifactName: "ReactWindows-final-nuget" + buildEnvironment: Continuous + isReleaseBuild: ${{ parameters.isReleaseBuild }} diff --git a/.ado/release.yml b/.ado/release.yml index 260a3ae270c..73928e6f5ed 100644 --- a/.ado/release.yml +++ b/.ado/release.yml @@ -1,6 +1,18 @@ +# +# The Release pipeline entry point. +# It releases npm packages to npmjs.com and NuGet packages to the public +# ms/react-native and ms/react-native-public ADO feeds and to nuget.org. +# +# The pipeline completion trigger is defined below in the pipeline resource. +# Do NOT add a build completion trigger in the ADO UI — UI triggers override +# YAML triggers and cause the pipeline to always run against the default branch +# with incorrect metadata (wrong commit message and branch). +# + name: RNW NuGet Release $(Date:yyyyMMdd).$(Rev:r) trigger: none +pr: none resources: pipelines: @@ -10,12 +22,18 @@ resources: trigger: branches: include: - - -1espublish + - main + - '0.74-stable' + - '0.81-stable' + - '0.82-stable' + - '0.83-stable' + - '0.84-stable' repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: @@ -23,18 +41,136 @@ extends: name: Azure-Pipelines-1ESPT-ExDShared image: windows-latest os: windows - customBuildTags: - - ES365AIMigrationTooling-Release stages: + # + # Gate stage — runs unconditionally for every trigger. + # It determines whether the Release stage should proceed and sets a + # descriptive build number so the pipeline history is easy to scan. + # + - stage: Gate + displayName: Evaluate release + jobs: + - job: Evaluate + displayName: Check if release should proceed + steps: + - checkout: none + + - pwsh: | + Write-Host "== Build Variables ==" + Write-Host "Build.Reason: $env:BUILD_REASON" + Write-Host "Build.SourceBranch: $env:BUILD_SOURCEBRANCH" + Write-Host "Build.SourceVersion: $env:BUILD_SOURCEVERSION" + Write-Host "Build.SourceVersionMessage: $env:BUILD_SOURCEVERSIONMESSAGE" + Write-Host "Build.BuildNumber: $env:BUILD_BUILDNUMBER" + Write-Host "Build.BuildId: $env:BUILD_BUILDID" + Write-Host "Build.DefinitionName: $env:BUILD_DEFINITIONNAME" + Write-Host "Build.Repository.Name: $env:BUILD_REPOSITORY_NAME" + Write-Host "System.TeamProject: $env:SYSTEM_TEAMPROJECT" + Write-Host "" + Write-Host "== Pipeline Resource: Publish ==" + Write-Host "Publish.runName: $env:PUBLISH_RUNNAME" + Write-Host "Publish.runID: $env:PUBLISH_RUNID" + Write-Host "Publish.sourceBranch: $env:PUBLISH_SOURCEBRANCH" + Write-Host "Publish.sourceCommit: $env:PUBLISH_SOURCECOMMIT" + Write-Host "Publish.pipelineID: $env:PUBLISH_PIPELINEID" + Write-Host "Publish.requestedFor: $env:PUBLISH_REQUESTEDFOR" + Write-Host "Publish.requestedForID: $env:PUBLISH_REQUESTEDFORID" + displayName: Log all pipeline variables + env: + BUILD_REASON: $(Build.Reason) + BUILD_SOURCEBRANCH: $(Build.SourceBranch) + BUILD_SOURCEVERSION: $(Build.SourceVersion) + BUILD_SOURCEVERSIONMESSAGE: $(Build.SourceVersionMessage) + BUILD_BUILDNUMBER: $(Build.BuildNumber) + BUILD_BUILDID: $(Build.BuildId) + BUILD_DEFINITIONNAME: $(Build.DefinitionName) + BUILD_REPOSITORY_NAME: $(Build.Repository.Name) + SYSTEM_TEAMPROJECT: $(System.TeamProject) + PUBLISH_RUNNAME: $(resources.pipeline.Publish.runName) + PUBLISH_RUNID: $(resources.pipeline.Publish.runID) + PUBLISH_SOURCEBRANCH: $(resources.pipeline.Publish.sourceBranch) + PUBLISH_SOURCECOMMIT: $(resources.pipeline.Publish.sourceCommit) + PUBLISH_PIPELINEID: $(resources.pipeline.Publish.pipelineID) + PUBLISH_REQUESTEDFOR: $(resources.pipeline.Publish.requestedFor) + PUBLISH_REQUESTEDFORID: $(resources.pipeline.Publish.requestedForID) + + - pwsh: | + $buildReason = $env:BUILD_REASON + # Use only the first line of the commit message + $sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim() + $publishRunName = $env:PUBLISH_RUN_NAME + $sourceBranch = $env:SOURCE_BRANCH -replace '^refs/heads/', '' + + # Extract the datestamp (e.g. "20260319.4") from the original build number + # which has the format "RNW NuGet Release 20260319.4" + $originalBuildNumber = $env:BUILD_BUILDNUMBER + $dateStamp = if ($originalBuildNumber -match '(\d{8}\.\d+)$') { $Matches[1] } else { "" } + + $shouldRelease = $false + $buildNumber = "" + + if ($buildReason -eq "Manual") { + $shouldRelease = $true + if ($publishRunName) { + $buildNumber = "$publishRunName ($sourceBranch) - $dateStamp" + } else { + $buildNumber = "Release ($sourceBranch) - $dateStamp" + } + } + elseif ($sourceMessage.StartsWith("RELEASE:")) { + $shouldRelease = $true + $buildNumber = "$publishRunName ($sourceBranch) - $dateStamp" + } + else { + $shouldRelease = $false + # Truncate commit message for readability + $shortMsg = $sourceMessage + if ($shortMsg.Length -gt 60) { + $shortMsg = $shortMsg.Substring(0, 57) + "..." + } + $buildNumber = "Skipped - $shortMsg ($sourceBranch) - $dateStamp" + } + + # Sanitize: ADO build numbers cannot contain " / : < > \ | ? @ * + # and cannot end with '.' + $buildNumber = $buildNumber -replace '["/:<>\\|?@*]', '_' + $buildNumber = $buildNumber.TrimEnd('.') + + Write-Host "shouldRelease: $shouldRelease" + Write-Host "buildNumber: $buildNumber" + + Write-Host "##vso[build.updatebuildnumber]$buildNumber" + Write-Host "##vso[task.setvariable variable=shouldRelease;isOutput=true]$shouldRelease" + name: gate + displayName: Determine release eligibility and set build number + env: + BUILD_REASON: $(Build.Reason) + BUILD_BUILDNUMBER: $(Build.BuildNumber) + SOURCE_MESSAGE: $(Build.SourceVersionMessage) + PUBLISH_RUN_NAME: $(resources.pipeline.Publish.runName) + SOURCE_BRANCH: $(resources.pipeline.Publish.sourceBranch) + + - script: echo Proceeding with release + displayName: RELEASING - proceeding to publish + condition: eq(variables['gate.shouldRelease'], 'True') + + - script: echo Skipping release + displayName: SKIPPED - not a RELEASE commit + condition: eq(variables['gate.shouldRelease'], 'False') + - stage: Release displayName: Publish artifacts + dependsOn: Gate + condition: eq(dependencies.Gate.outputs['Evaluate.gate.shouldRelease'], 'True') jobs: - job: PushNpm displayName: npmjs.com - Publish npm packages variables: - group: RNW Secrets - timeoutInMinutes: 0 + timeoutInMinutes: 30 templateContext: + type: releaseJob + isProduction: true inputs: - input: pipelineArtifact pipeline: 'Publish' @@ -45,25 +181,23 @@ extends: artifactName: 'VersionEnvVars' targetPath: '$(Pipeline.Workspace)/VersionEnvVars' steps: - - checkout: self - clean: false - task: CmdLine@2 displayName: Apply version variables inputs: script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js - script: dir /s "$(Pipeline.Workspace)\published-packages" displayName: Show npm packages before cleanup - - script: node .ado/scripts/npmPack.js --no-pack --check-npm --no-color "$(Pipeline.Workspace)\published-packages" + - script: node "$(Pipeline.Workspace)\VersionEnvVars\npmPack.js" --no-pack --check-npm --no-color "$(Pipeline.Workspace)\published-packages" displayName: Remove already published packages - script: dir /s "$(Pipeline.Workspace)\published-packages" displayName: Show npm packages after cleanup - - powershell: | + - pwsh: | $tgzFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\published-packages" -Filter "*.tgz" -Recurse $tgzCount = $tgzFiles.Count Write-Host "Found $tgzCount .tgz files" Write-Host "##vso[task.setvariable variable=HasPackagesToPublish]$($tgzCount -gt 0)" displayName: Check if there are packages to publish - - task: 'SFP.release-tasks.custom-build-release-task.EsrpRelease@10' + - task: 'EsrpRelease@11' displayName: 'ESRP Release to npmjs.com' condition: and(succeeded(), ne(variables['NpmDistTag'], ''), eq(variables['HasPackagesToPublish'], 'true')) inputs: @@ -81,77 +215,129 @@ extends: approvers: 'khosany@microsoft.com' - job: PushPrivateAdo - displayName: ADO - react-native - timeoutInMinutes: 0 + displayName: ADO - nuget - react-native + timeoutInMinutes: 30 templateContext: + type: releaseJob + isProduction: true inputs: - input: pipelineArtifact pipeline: 'Publish' artifactName: 'ReactWindows-final-nuget' targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' steps: - - checkout: none - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' - - task: NuGetAuthenticate@1 - displayName: NuGet Authenticate - inputs: - nuGetServiceConnections: ms/react-native ADO Feed - - task: CmdLine@2 - displayName: NuGet push (react-native) - inputs: - script: nuget.exe push *.nupkg -ApiKey AzureArtifacts -Source https://pkgs.dev.azure.com/ms/_packaging/react-native/nuget/v3/index.json -NonInteractive -Verbosity Detailed -SkipDuplicate -NoSymbols - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + - template: .ado/templates/publish-nuget-to-ado-feed.yml@self + parameters: + endpointId: 'a7e33797-4804-4a1d-911d-5bd325e50a85' + nugetFeedUrl: 'https://pkgs.dev.azure.com/ms/_packaging/react-native/nuget/v3/index.json' + packageParentPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + packagesToPush: '$(Pipeline.Workspace)/ReactWindows-final-nuget/*.nupkg' + publishFeedCredentials: 'ms/react-native ADO Feed' + feedDisplayName: 'ms/react-native' - job: PushPublicAdo - displayName: ADO - react-native-public - timeoutInMinutes: 0 + displayName: ADO - nuget - react-native-public + timeoutInMinutes: 30 templateContext: + type: releaseJob + isProduction: true inputs: - input: pipelineArtifact pipeline: 'Publish' artifactName: 'ReactWindows-final-nuget' targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' steps: - - checkout: none - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' - - task: NuGetAuthenticate@1 - displayName: NuGet Authenticate - inputs: - nuGetServiceConnections: ms/react-native-public ADO Feed - - task: CmdLine@2 - displayName: NuGet push (react-native-public) - inputs: - script: nuget.exe push *.nupkg -ApiKey AzureArtifacts -Source https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json -NonInteractive -Verbosity Detailed -SkipDuplicate -NoSymbols - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + - template: .ado/templates/publish-nuget-to-ado-feed.yml@self + parameters: + endpointId: '9a2456d0-c163-405b-be24-c03fd74b155a' + nugetFeedUrl: 'https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json' + packageParentPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + packagesToPush: '$(Pipeline.Workspace)/ReactWindows-final-nuget/*.nupkg' + publishFeedCredentials: 'ms/react-native-public ADO Feed' + feedDisplayName: 'ms/react-native-public' - job: PushNuGetOrg displayName: nuget.org - Push nuget packages variables: - group: RNW Secrets - timeoutInMinutes: 0 + timeoutInMinutes: 30 templateContext: + type: releaseJob + isProduction: true inputs: - input: pipelineArtifact pipeline: 'Publish' artifactName: 'ReactWindows-final-nuget' targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' steps: - - checkout: none - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' - - task: CmdLine@2 + displayName: 'Use NuGet' + - pwsh: nuget.exe SetApiKey "$env:NUGET_API_KEY" displayName: NuGet SetApiKey (nuget.org) - inputs: - script: nuget.exe SetApiKey $(nugetorg-apiKey-push) - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget - - task: PowerShell@2 + workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + env: + NUGET_API_KEY: $(nugetorg-apiKey-push) + - script: dir /S "$(Pipeline.Workspace)\ReactWindows-final-nuget" + displayName: Show directory contents + - script: nuget.exe push .\Microsoft.ReactNative.*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -NoSymbol -NonInteractive -Verbosity Detailed displayName: NuGet push (nuget.org) + workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + + - job: PublishSymbols + displayName: Publish PDB Symbols to Symbol Server + timeoutInMinutes: 30 + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + pipeline: 'Publish' + artifactName: 'ReactWindows-final-nuget' + targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + steps: + - pwsh: | + # Extract PDB files from all NuGet packages (.nupkg are ZIP archives) + $nugetDir = "$(Pipeline.Workspace)/ReactWindows-final-nuget" + $symbolsDir = "$(Pipeline.Workspace)/symbols" + New-Item -ItemType Directory -Path $symbolsDir -Force | Out-Null + + $nupkgs = Get-ChildItem "$nugetDir/*.nupkg" + Write-Host "Found $($nupkgs.Count) NuGet packages" + + foreach ($nupkg in $nupkgs) { + Write-Host "Extracting PDBs from: $($nupkg.Name)" + $extractDir = "$symbolsDir/$($nupkg.BaseName)" + # Rename to .zip for Expand-Archive compatibility + $zipPath = "$nugetDir/$($nupkg.BaseName).zip" + Copy-Item $nupkg.FullName $zipPath + Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force + Remove-Item $zipPath + } + + # Show extracted PDBs + $pdbs = Get-ChildItem "$symbolsDir" -Recurse -Filter "*.pdb" + Write-Host "`nFound $($pdbs.Count) PDB files:" + foreach ($pdb in $pdbs) { + Write-Host " $($pdb.FullName) ($([math]::Round($pdb.Length / 1MB, 2)) MB)" + } + + if ($pdbs.Count -eq 0) { + Write-Host "##vso[task.logissue type=warning]No PDB files found in NuGet packages" + } + displayName: Extract PDBs from NuGet packages + + - task: PublishSymbols@2 + displayName: 'Publish Symbols to Microsoft Symbol Server' + continueOnError: true inputs: - targetType: inline - errorActionPreference: silentlyContinue - script: | - if (Get-ChildItem -Path .\ -Filter '*0.0.0-canary*' -ErrorAction SilentlyContinue) { Write-Output "Canary builds found, exiting."; return 0; } - nuget.exe push .\Microsoft.ReactNative.*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -NoSymbol -NonInteractive -Verbosity Detailed - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget + UseNetCoreClientTool: true + ConnectedServiceName: Office-React-Native-Windows-Bot + SymbolsFolder: '$(Pipeline.Workspace)/symbols' + SearchPattern: '**/*.pdb' + SymbolServerType: 'TeamServices' + IndexSources: false # SourceLink is already embedded in PDBs at compile time + SymbolsProduct: 'ReactNativeWindows' + SymbolsVersion: '$(Build.BuildNumber)' + SymbolsArtifactName: 'ReactNativeWindows-Symbols-$(Build.BuildId)' + DetailedLog: true + TreatNotIndexedAsWarning: false diff --git a/.ado/scripts/build.js b/.ado/scripts/build.js new file mode 100644 index 00000000000..47df62d2c65 --- /dev/null +++ b/.ado/scripts/build.js @@ -0,0 +1,195 @@ +// .ado/scripts/build.js +// BinSkim security validation for React Native Windows. +// +// Can be run locally or in the Azure DevOps pipeline. +// Usage: node .ado/scripts/build.js --binskim [--platform x64] [--configuration Release] [--target desktop|universal] + +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { parseArgs } from "node:util"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const sourcesPath = path.resolve(__dirname, "..", ".."); + +const binskimPackageName = "Microsoft.CodeAnalysis.BinSkim"; +const binskimVersion = "4.4.9"; +const binskimNuGetSource = "https://api.nuget.org/v3/index.json"; + +const options = { + help: { type: "boolean", default: false }, + binskim: { type: "boolean", default: false }, + platform: { type: "string", default: "x64" }, + configuration: { type: "string", default: "Release" }, + target: { type: "string", default: "" }, // "desktop", "universal", or "" for both +}; + +const args = parseArgs({ options, allowPositionals: false }).values; +if (args.help) { printHelp(); process.exit(0); } + +main(); + +function main() { + if (!args.binskim) { + console.log("No action specified. Use --binskim to run BinSkim analysis."); + console.log("Run with --help for usage details."); + return; + } + + const toolsPath = path.join(sourcesPath, "tools"); + const targetRoot = path.join(sourcesPath, "vnext", "target"); + const plat = args.platform; + const config = args.configuration; + + // Collect binaries based on --target flag. + const candidates = []; + if (!args.target || args.target === "desktop") { + candidates.push( + path.join(targetRoot, plat, config, "React.Windows.Desktop.DLL", "react-native-win32.dll") + ); + } + if (!args.target || args.target === "universal") { + candidates.push( + path.join(targetRoot, plat, config, "Microsoft.ReactNative", "Microsoft.ReactNative.dll") + ); + } + + runBinSkim({ toolsPath, candidates }); +} + +function printHelp() { + console.log(` +Usage: node build.js --binskim [options] + +Options: + --binskim Run BinSkim security analysis on shipped DLLs + --platform Build platform (default: x64) + --configuration Build configuration (default: Release) + --target Target to scan: desktop, universal, or omit for both + --help Show this help message +`); +} + +function ensureDir(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +function ensureNuGet(toolsPath) { + // Check if nuget.exe is already in the tools directory. + const localNuGet = path.join(toolsPath, "nuget.exe"); + if (fs.existsSync(localNuGet)) { + return localNuGet; + } + + // Check if nuget.exe is in PATH. + try { + const result = execSync("where nuget.exe", { encoding: "utf8" }).trim(); + if (result) { + const firstLine = result.split(/\r?\n/)[0]; + console.log(`Found nuget.exe in PATH: ${firstLine}`); + return firstLine; + } + } catch { + // Not in PATH, download it. + } + + // Download nuget.exe from the official distribution. + ensureDir(toolsPath); + console.log(`Downloading nuget.exe to: ${localNuGet}`); + execSync( + `powershell.exe -NoLogo -NoProfile -Command ` + + `"[Net.ServicePointManager]::SecurityProtocol = ` + + `[Net.SecurityProtocolType]::Tls12; ` + + `Invoke-WebRequest -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' ` + + `-OutFile '${localNuGet}' -UseBasicParsing"`, + { stdio: "inherit" }, + ); + + if (!fs.existsSync(localNuGet)) { + throw new Error("Failed to download nuget.exe"); + } + return localNuGet; +} + +function runBinSkim({ toolsPath, candidates }) { + // Determine BinSkim.exe path within the NuGet package. + const binskimDir = path.join(toolsPath, "binskim"); + const binskimPkgDir = path.join( + binskimDir, + `${binskimPackageName}.${binskimVersion}`, + ); + + // Find BinSkim.exe under the package tools directory. The runtime folder + // name varies by package version (e.g. netcoreapp3.1, net9.0). + function findBinskimExe() { + const toolsDir = path.join(binskimPkgDir, "tools"); + if (!fs.existsSync(toolsDir)) return null; + for (const runtime of fs.readdirSync(toolsDir)) { + const candidate = path.join(toolsDir, runtime, "win-x64", "BinSkim.exe"); + if (fs.existsSync(candidate)) return candidate; + } + return null; + } + + let binskimExe = findBinskimExe(); + + // Install BinSkim NuGet package if not already present. + if (!binskimExe) { + ensureDir(binskimDir); + const nuget = ensureNuGet(toolsPath); + console.log( + `Installing ${binskimPackageName} ${binskimVersion} from ${binskimNuGetSource}...`, + ); + execSync( + `"${nuget}" install ${binskimPackageName}` + + ` -Version ${binskimVersion}` + + ` -Source "${binskimNuGetSource}"` + + ` -OutputDirectory "${binskimDir}"`, + { stdio: "inherit" }, + ); + binskimExe = findBinskimExe(); + if (!binskimExe) { + throw new Error( + `BinSkim.exe not found after install in: ${binskimPkgDir}`, + ); + } + } + + // Filter to binaries that exist on disk. + const binaries = candidates.filter((f) => fs.existsSync(f)); + + if (binaries.length === 0) { + console.warn("BinSkim: No binaries found to scan. Skipping."); + return; + } + + console.log(`\nRunning BinSkim ${binskimVersion} on ${binaries.length} binaries:`); + for (const b of binaries) { + console.log(` ${path.basename(b)}`); + } + + const fileArgs = binaries.map((b) => `"${b}"`).join(" "); + try { + execSync( + `"${binskimExe}" analyze` + + ` --config default` + + ` --ignorePdbLoadError` + + ` --ignorePELoadErrors True` + + ` --hashes` + + ` --statistics` + + ` --disable-telemetry True` + + ` ${fileArgs}`, + { stdio: "inherit" }, + ); + console.log("\nBinSkim: All rules passed."); + } catch (error) { + console.error( + `\nBinSkim failed with exit code: ${error.status || "unknown"}`, + ); + process.exit(error.status || 1); + } +} diff --git a/.ado/scripts/npmPack.js b/.ado/scripts/npmPack.js index a89519b6f04..5a3c141ac39 100644 --- a/.ado/scripts/npmPack.js +++ b/.ado/scripts/npmPack.js @@ -391,14 +391,19 @@ function main() { const targetDirArg = args.positionals[0]; try { - // Find repo root - const repoRoot = findEnlistmentRoot(); - console.log(`${colorize('Repository root:', colors.bright)} ${repoRoot}`); + // Find repo root (not needed when --no-pack is used with an absolute path) + const repoRoot = (noPackFlag && targetDirArg && path.isAbsolute(targetDirArg)) + ? null + : findEnlistmentRoot(); + + if (repoRoot) { + console.log(`${colorize('Repository root:', colors.bright)} ${repoRoot}`); + } // Determine target directory const targetDir = targetDirArg - ? path.resolve(repoRoot, targetDirArg) - : path.join(repoRoot, 'npm-pkgs'); + ? (repoRoot ? path.resolve(repoRoot, targetDirArg) : path.resolve(targetDirArg)) + : path.join(/** @type {string} */ (repoRoot), 'npm-pkgs'); console.log(`${colorize('Target directory:', colors.bright)} ${targetDir}`); diff --git a/.ado/stages.yml b/.ado/stages.yml deleted file mode 100644 index 172876377dd..00000000000 --- a/.ado/stages.yml +++ /dev/null @@ -1,85 +0,0 @@ -parameters: - - name: buildEnvironment - type: string - default: PullRequest - values: - - PullRequest - - SecurePullRequest - - Continuous - - name: AgentPool - type: object - -stages: - - stage: Setup - jobs: - - template: jobs/setup.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - - - stage: Build - displayName: Build 🔨 - dependsOn: Setup - jobs: - - template: jobs/universal.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: jobs/desktop.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: jobs/nuget-desktop.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: jobs/cli-init-windows.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - buildNuGetOnly: true - - # Attack Surface Analyzer for SDL compliance - - template: jobs/attack-surface-analyzer.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - complianceWarnOnly: true - - - stage: IntegrationTests - displayName: Tests 🧪 - dependsOn: Setup - jobs: - - template: jobs/linting.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: jobs/node-tests.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: jobs/playground.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: jobs/e2e-test.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - stage: CLI - displayName: Cli 🖥 - dependsOn: Setup - jobs: - - template: jobs/cli-init-windows.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - buildNuGetOnly: false - - - template: jobs/macos-tests.yml diff --git a/.ado/templates/checkout-shallow.yml b/.ado/templates/checkout-shallow.yml index 21460b9c88d..20c685f9395 100644 --- a/.ado/templates/checkout-shallow.yml +++ b/.ado/templates/checkout-shallow.yml @@ -2,7 +2,7 @@ # without full history. steps: - checkout: self - fetchDepth: 10 # Buffer to avoid race condition with AZP jobs started against merge branch + fetchDepth: 1 clean: false submodules: false lfs: false diff --git a/.ado/templates/cleanup-certificate.yml b/.ado/templates/cleanup-certificate.yml index 525276acb9c..70c92c7f5d3 100644 --- a/.ado/templates/cleanup-certificate.yml +++ b/.ado/templates/cleanup-certificate.yml @@ -1,5 +1,5 @@ steps: - - powershell: | + - pwsh: | $PfxPath = Join-Path -Path $(Build.SourcesDirectory) -ChildPath EncodedKey.pfx Write-Host $PfxPath Remove-Item -path $PfxPath diff --git a/.ado/templates/compute-beachball-branch-name.yml b/.ado/templates/compute-beachball-branch-name.yml index a9fb43462f1..93a578a1e11 100644 --- a/.ado/templates/compute-beachball-branch-name.yml +++ b/.ado/templates/compute-beachball-branch-name.yml @@ -1,12 +1,16 @@ steps: - - powershell: | - Write-Host "Setting BeachBallBranchName to $(System.PullRequest.TargetBranch)" - Write-Host "##vso[task.setvariable variable=BeachBallBranchName]$(System.PullRequest.TargetBranch)" + - pwsh: | + Write-Host "Setting BeachBallBranchName to $env:PR_TARGET_BRANCH" + Write-Host "##vso[task.setvariable variable=BeachBallBranchName]$env:PR_TARGET_BRANCH" displayName: Set BeachBallBranchName for Pull Request condition: ${{ eq(variables['Build.Reason'], 'PullRequest') }} + env: + PR_TARGET_BRANCH: $(System.PullRequest.TargetBranch) - - powershell: | - Write-Host "Setting BeachBallBranchName to $(Build.SourceBranchName)" - Write-Host "##vso[task.setvariable variable=BeachBallBranchName]$(Build.SourceBranchName)" + - pwsh: | + Write-Host "Setting BeachBallBranchName to $env:SOURCE_BRANCH_NAME" + Write-Host "##vso[task.setvariable variable=BeachBallBranchName]$env:SOURCE_BRANCH_NAME" displayName: Set BeachBallBranchName for CI condition: ${{ ne(variables['Build.Reason'], 'PullRequest') }} + env: + SOURCE_BRANCH_NAME: $(Build.SourceBranchName) diff --git a/.ado/templates/detect-nuget-lockfile-changes.yml b/.ado/templates/detect-nuget-lockfile-changes.yml index daee55476d1..068f625b219 100644 --- a/.ado/templates/detect-nuget-lockfile-changes.yml +++ b/.ado/templates/detect-nuget-lockfile-changes.yml @@ -4,7 +4,7 @@ parameters: default: true steps: - - powershell: | + - pwsh: | & git add */packages*.lock.json $changed = git status --porcelain=v1 */packages*.lock.json if ($changed -ne $null) { diff --git a/.ado/templates/discover-google-test-adapter.yml b/.ado/templates/discover-google-test-adapter.yml index d2e63281ac1..2a0c05a100e 100644 --- a/.ado/templates/discover-google-test-adapter.yml +++ b/.ado/templates/discover-google-test-adapter.yml @@ -1,6 +1,6 @@ steps: - - powershell: | + - pwsh: | $vsExtensionPath="${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\"; $GoogleTestAdapterPath=(Get-ChildItem $vsExtensionPath -Directory | Where-Object -FilterScript {Test-Path (Join-Path -Path $_.FullName -ChildPath "GoogleTestAdapter.Core.dll")}).FullName diff --git a/.ado/templates/enable-experimental-winui3.yml b/.ado/templates/enable-experimental-winui3.yml index f65dd14bed3..a5cba36dae1 100644 --- a/.ado/templates/enable-experimental-winui3.yml +++ b/.ado/templates/enable-experimental-winui3.yml @@ -18,6 +18,7 @@ steps: inputs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\EnableInternalWinAppSDKFeed.ps1 + pwsh: true - task: NuGetAuthenticate@1 displayName: 'NuGet Authenticate Internal WinAppSDK Feed' diff --git a/.ado/templates/esrp-codesign-binaries.yml b/.ado/templates/esrp-codesign-binaries.yml new file mode 100644 index 00000000000..9c8c03adcf5 --- /dev/null +++ b/.ado/templates/esrp-codesign-binaries.yml @@ -0,0 +1,45 @@ +parameters: + - name: displayName + type: string + - name: folderPath + type: string + - name: pattern + type: string + +steps: + - task: EsrpCodeSigning@6 + displayName: ${{ parameters.displayName }} + inputs: + ConnectedServiceName: 'ESRP-CodeSigning-OGX-JSHost-RNW' + AppRegistrationClientId: '0a35e01f-eadf-420a-a2bf-def002ba898d' + AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' + AuthAKVName: 'OGX-JSHost-KV' + AuthCertName: 'OGX-JSHost-Auth4' + AuthSignCertName: 'OGX-JSHost-Sign3' + FolderPath: ${{ parameters.folderPath }} + Pattern: ${{ parameters.pattern }} + UseMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode" : "CP-230012", + "OperationCode" : "SigntoolSign", + "Parameters" : { + "OpusName" : "Microsoft", + "OpusInfo" : "http://www.microsoft.com", + "FileDigest" : "/fd \"SHA256\"", + "PageHash" : "/PH", + "TimeStamp" : "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + }, + "ToolName" : "sign", + "ToolVersion" : "1.0" + }, + { + "KeyCode" : "CP-230012", + "OperationCode" : "SigntoolVerify", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + } + ] diff --git a/.ado/templates/esrp-codesign-nuget.yml b/.ado/templates/esrp-codesign-nuget.yml new file mode 100644 index 00000000000..3666061449f --- /dev/null +++ b/.ado/templates/esrp-codesign-nuget.yml @@ -0,0 +1,39 @@ +parameters: + - name: displayName + type: string + - name: folderPath + type: string + - name: pattern + type: string + +steps: + - task: EsrpCodeSigning@6 + displayName: ${{ parameters.displayName }} + inputs: + ConnectedServiceName: 'ESRP-CodeSigning-OGX-JSHost-RNW' + AppRegistrationClientId: '0a35e01f-eadf-420a-a2bf-def002ba898d' + AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' + AuthAKVName: 'OGX-JSHost-KV' + AuthCertName: 'OGX-JSHost-Auth4' + AuthSignCertName: 'OGX-JSHost-Sign3' + FolderPath: ${{ parameters.folderPath }} + Pattern: ${{ parameters.pattern }} + UseMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode" : "CP-401405", + "OperationCode" : "NuGetSign", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + }, + { + "KeyCode" : "CP-401405", + "OperationCode" : "NuGetVerify", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + } + ] diff --git a/.ado/templates/msbuild-sln.yml b/.ado/templates/msbuild-sln.yml index 56d7bc62222..5b52eca5aaf 100644 --- a/.ado/templates/msbuild-sln.yml +++ b/.ado/templates/msbuild-sln.yml @@ -18,12 +18,12 @@ parameters: parallelBuild: true steps: - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=BuildLogDirectory]$(Build.BinariesDirectory)\${{ parameters.buildPlatform }}\${{ parameters.buildConfiguration }}\BuildLogs" Write-Host "##vso[task.setvariable variable=MsBuildWarnAsErrorArgument]/warnaserror" displayName: Set Log directory and warn as error - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=MsBuildWarnAsErrorArgument]" condition: not(eq('${{parameters.warnAsError}}', 'true')) displayName: Disable WarnAsError diff --git a/.ado/templates/prep-and-pack-nuget.yml b/.ado/templates/prep-and-pack-nuget.yml index b3628fe82f5..112a2b1ac6b 100644 --- a/.ado/templates/prep-and-pack-nuget.yml +++ b/.ado/templates/prep-and-pack-nuget.yml @@ -1,9 +1,6 @@ parameters: - name: artifactName type: string - - name: artifactName2 - type: string - default: '' - name: slices type: object @@ -29,9 +26,6 @@ parameters: type: boolean default: false - - name: signMicrosoft - type: boolean - default: false steps: - pwsh: | @@ -56,12 +50,6 @@ steps: inputs: artifact: ${{ parameters.artifactName }}.${{ slice.platform }}.${{ slice.configuration }} path: ${{parameters.nugetroot}}/${{ parameters.artifactName }}/${{ slice.platform }}/${{ slice.configuration }} - - ${{ if ne(parameters.artifactName2, '') }}: - - task: DownloadPipelineArtifact@2 - displayName: 'Download ${{ parameters.artifactName2 }}.${{ slice.platform }}.${{ slice.configuration }}' - inputs: - artifact: ${{ parameters.artifactName2 }}.${{ slice.platform }}.${{ slice.configuration }} - path: ${{parameters.nugetroot}}/${{ parameters.artifactName2 }}/${{ slice.platform }}/${{ slice.configuration }} - pwsh: yarn build displayName: Run yarn build @@ -71,6 +59,7 @@ steps: inputs: filePath: vnext/Scripts/Tfs/Layout-MSRN-Headers.ps1 arguments: -TargetRoot ${{parameters.nugetroot}} + pwsh: true - ${{ if eq(parameters.packDesktop, true) }}: - task: PowerShell@2 @@ -78,9 +67,10 @@ steps: inputs: filePath: vnext/Scripts/Tfs/Layout-Desktop-Headers.ps1 arguments: -TargetRoot ${{parameters.nugetroot}} + pwsh: true - ${{ if or(eq(parameters.packMicrosoftReactNative, true), eq(parameters.packMicrosoftReactNativeCxx, true)) }}: - - powershell: | + - pwsh: | (Get-Content -Path ${{parameters.nugetroot}}\Microsoft.ReactNative.VersionCheck.targets) -replace '\$\$nuGetPackageVersion\$\$', '${{parameters.npmVersion}}' | Set-Content -Path ${{parameters.nugetroot}}\Microsoft.ReactNative.VersionCheck.targets displayName: Patch version check file with version ${{parameters.npmVersion}} @@ -99,8 +89,6 @@ steps: outputPackage: Microsoft.ReactNative slices: $(releaseSlices) packageVersion: ${{parameters.npmVersion}} - codesignBinaries: ${{ parameters.signMicrosoft }} - codesignNuget: ${{ parameters.signMicrosoft }} buildProperties: CommitId=${{parameters.publishCommitId}};nugetroot=${{parameters.nugetroot}};baseconfiguration=Release;baseplatform=$(releaseBasePlatform) @@ -110,5 +98,4 @@ steps: outputPackage: Microsoft.ReactNative.Cxx packageVersion: ${{parameters.npmVersion}} buildProperties: CommitId=${{parameters.publishCommitId}};nugetroot=${{parameters.nugetroot}};baseconfiguration=$(baseConfiguration);baseplatform=$(basePlatform) - codesignNuget: ${{ parameters.signMicrosoft }} diff --git a/.ado/templates/prep-and-pack-single.yml b/.ado/templates/prep-and-pack-single.yml index e07757bdbfd..9bc1246e211 100644 --- a/.ado/templates/prep-and-pack-single.yml +++ b/.ado/templates/prep-and-pack-single.yml @@ -2,7 +2,7 @@ parameters: # Required: Name to publish the NuGet Package As - name: outputPackage type: string - + # Required: NPM-matching version - name: packageVersion type: string @@ -12,40 +12,24 @@ parameters: type: string default: '' - # Optional: Pattern of binaries within the artifact to sign as part of this - # NuGet package. Defaults to .dll, .winmd, .exe matching the nuspec name - - name: binariesToSign - type: string - default: '' - # Optional: Excludes platform-specific files from the NuSpec of they are not # included in slices - name: slices type: string default: '' - # Optional: Properties to pass to nuspec + # Optional: Properties to pass to nuspec - name: buildProperties type: string default: '' - # Optional: Whether to sign binaries - - name: codesignBinaries - type: boolean - default: false - - # Optional: Whether to sign the NuGet packag - - name: codesignNuget - type: boolean - default: false - steps: - - powershell: gci $(System.DefaultWorkingDirectory)/NugetRoot + - pwsh: gci $(System.DefaultWorkingDirectory)/NugetRoot displayName: List files in NugetRoot - ${{ if ne(parameters.slices, '') }}: - - powershell: > + - pwsh: > .\StripAdditionalPlatformsFromNuspec.ps1 -nuspec ${{ coalesce(parameters.nuspec, parameters.outputPackage) }}.nuspec -outfile ${{ parameters.outputPackage }}.nuspec @@ -54,50 +38,8 @@ steps: displayName: '${{ parameters.outputPackage }} - Strip slices from nuspec' workingDirectory: $(System.DefaultWorkingDirectory)/NugetRoot - - ${{ if eq(parameters.codesignBinaries, true) }}: - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 - displayName: '${{ parameters.outputPackage }} CodeSign Binaries' - inputs: - ConnectedServiceName: 'ESRP-CodeSigning-OGX-JSHost-RNW' - AppRegistrationClientId: '0a35e01f-eadf-420a-a2bf-def002ba898d' - AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - AuthAKVName: 'OGX-JSHost-KV' - AuthCertName: 'OGX-JSHost-Auth4' - AuthSignCertName: 'OGX-JSHost-Sign3' - FolderPath: $(System.DefaultWorkingDirectory)/NugetRoot - # Recursively finds files matching these patterns: - ${{ if ne(parameters.binariesToSign, '') }}: - Pattern: ${{ parameters.binariesToSign }} - ${{ else }}: - Pattern: | - **/${{ coalesce(parameters.nuspec, parameters.outputPackage) }}.dll - **/${{ coalesce(parameters.nuspec, parameters.outputPackage) }}.winmd - **/${{ coalesce(parameters.nuspec, parameters.outputPackage) }}.exe - UseMinimatch: true - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "KeyCode" : "CP-230012", - "OperationCode" : "SigntoolSign", - "Parameters" : { - "OpusName" : "Microsoft", - "OpusInfo" : "http://www.microsoft.com", - "FileDigest" : "/fd \"SHA256\"", - "PageHash" : "/PH", - "TimeStamp" : "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" - }, - "ToolName" : "sign", - "ToolVersion" : "1.0" - }, - { - "KeyCode" : "CP-230012", - "OperationCode" : "SigntoolVerify", - "Parameters" : {}, - "ToolName" : "sign", - "ToolVersion" : "1.0" - } - ] + # Binary signing is done in build jobs (ESRP CodeSign before artifact upload) + # NuGet signing is done in batch in publish.yml (single ESRP call for all .nupkg) # NuGetCommand@2 workaround: https://developercommunity.visualstudio.com/content/problem/288534/vsts-yaml-build-failure-the-task-name-nugetcommand.html - task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2 @@ -109,40 +51,7 @@ steps: packDestination: $(System.DefaultWorkingDirectory)/NugetRootFinal buildProperties: version=${{ parameters.packageVersion }};id=${{ parameters.outputPackage }};${{ parameters.buildProperties }} - - ${{ if eq(parameters.codesignNuget, true) }}: - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 - displayName: '${{ parameters.outputPackage }} CodeSign NuGet' - inputs: - ConnectedServiceName: 'ESRP-CodeSigning-OGX-JSHost-RNW' - AppRegistrationClientId: '0a35e01f-eadf-420a-a2bf-def002ba898d' - AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - AuthAKVName: 'OGX-JSHost-KV' - AuthCertName: 'OGX-JSHost-Auth4' - AuthSignCertName: 'OGX-JSHost-Sign3' - FolderPath: $(System.DefaultWorkingDirectory)/NugetRootFinal - Pattern: | - **/${{ parameters.outputPackage }}.${{ parameters.packageVersion }}.nupkg - UseMinimatch: true - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "KeyCode" : "CP-401405", - "OperationCode" : "NuGetSign", - "Parameters" : {}, - "ToolName" : "sign", - "ToolVersion" : "1.0" - }, - { - "KeyCode" : "CP-401405", - "OperationCode" : "NuGetVerify", - "Parameters" : {}, - "ToolName" : "sign", - "ToolVersion" : "1.0" - } - ] - - - powershell: gci $(System.DefaultWorkingDirectory)/NugetRootFinal + - pwsh: gci $(System.DefaultWorkingDirectory)/NugetRootFinal displayName: List files in NugetRootFinal - script: | diff --git a/.ado/templates/prepare-build-env.yml b/.ado/templates/prepare-build-env.yml index ab5dc7e6bc8..2e81a4bad40 100644 --- a/.ado/templates/prepare-build-env.yml +++ b/.ado/templates/prepare-build-env.yml @@ -36,27 +36,27 @@ steps: configuration: ${{ parameters.configuration }} buildEnvironment: ${{ parameters.buildEnvironment }} - - powershell: | + - pwsh: | & reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /s & reg query "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /s & reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug" /s Enable-WindowsErrorReporting displayName: Check and enable Windows Error Reporting - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=CrashDumpRootPath]$(Build.StagingDirectory)\CrashDumps" New-Item -Path '$(Build.StagingDirectory)\CrashDumps' -ItemType Directory displayName: Set CrashDumpRootPath - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=MSBUILDDEBUGPATH]$(CrashDumpRootPath)" displayName: Set MSBUILDDEBUGPATH - - powershell: | + - pwsh: | Write-Host "##vso[task.setvariable variable=ProcDumpPath]$(Build.StagingDirectory)\Procdump" displayName: Set ProcDumpPath - - powershell: | + - pwsh: | & $(Build.SourcesDirectory)\.ado\scripts\RunProcDump.ps1 -ProcDumpArgs @("-mm", "-i", "$(CrashDumpRootPath)") -ProcDumpInstallPath "$(ProcDumpPath)" -Verbose displayName: Setup ProcDump as AeDebug \ No newline at end of file diff --git a/.ado/templates/prepare-js-env.yml b/.ado/templates/prepare-js-env.yml index 89c8724575f..eefe6581503 100644 --- a/.ado/templates/prepare-js-env.yml +++ b/.ado/templates/prepare-js-env.yml @@ -11,7 +11,7 @@ steps: - task: NodeTool@0 displayName: Set Node Version inputs: - versionSpec: '22.x' + versionSpec: '24.x' - script: if not exist %APPDATA%\npm (mkdir %APPDATA%\npm) displayName: Ensure npm directory for npx commands diff --git a/.ado/templates/publish-nuget-to-ado-feed.yml b/.ado/templates/publish-nuget-to-ado-feed.yml new file mode 100644 index 00000000000..c716639638d --- /dev/null +++ b/.ado/templates/publish-nuget-to-ado-feed.yml @@ -0,0 +1,106 @@ +parameters: +- name: azureSubscription + type: string + default: 'Office-React-Native-Windows-Bot' +- name: endpointId + type: string +- name: nugetFeedUrl + type: string +- name: packageParentPath + type: string +- name: packagesToPush + type: string +- name: publishFeedCredentials + type: string +- name: feedDisplayName + type: string + +steps: +- script: dir /S "${{ parameters.packageParentPath }}" + displayName: Show NuGet packages before cleanup + +- task: AzureCLI@2 + displayName: Override NuGet credentials with Managed Identity + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + visibleAzLogin: false + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + $accessToken = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv + # Set the access token as a secret, so it doesn't get leaked in the logs + Write-Host "##vso[task.setsecret]$accessToken" + # Override the apitoken of the nuget service connection, for the duration of this stage + Write-Host "##vso[task.setendpoint id=${{ parameters.endpointId }};field=authParameter;key=apitoken]$accessToken" + # Also expose the token for the pre-push duplicate check + Write-Host "##vso[task.setvariable variable=NuGetAccessToken;issecret=true]$accessToken" + +- pwsh: | + Add-Type -AssemblyName System.IO.Compression.FileSystem + + $feedUrl = "${{ parameters.nugetFeedUrl }}" + $token = "$(NuGetAccessToken)" + $headers = @{ Authorization = "Bearer $token" } + + # Discover the flat container (PackageBaseAddress) URL from the V3 index + $index = Invoke-RestMethod -Uri $feedUrl -Headers $headers + $baseAddress = ($index.resources | + Where-Object { $_.'@type' -like 'PackageBaseAddress*' } | + Select-Object -First 1).'@id' + if (-not $baseAddress) { throw "Could not find PackageBaseAddress in NuGet V3 index at $feedUrl" } + if (-not $baseAddress.EndsWith('/')) { $baseAddress += '/' } + Write-Host "PackageBaseAddress: $baseAddress" + + $nupkgs = Get-ChildItem -Path "${{ parameters.packageParentPath }}" -Filter "*.nupkg" -Recurse + $removedCount = 0 + + foreach ($file in $nupkgs) { + # Read the .nuspec from inside the nupkg (zip) to get the exact id and version + $zip = [System.IO.Compression.ZipFile]::OpenRead($file.FullName) + try { + $nuspecEntry = $zip.Entries | Where-Object { $_.FullName -like "*.nuspec" } | Select-Object -First 1 + $reader = New-Object System.IO.StreamReader($nuspecEntry.Open()) + [xml]$nuspec = $reader.ReadToEnd() + $reader.Close() + } finally { $zip.Dispose() } + + $id = $nuspec.package.metadata.id + $version = $nuspec.package.metadata.version + + # Query the flat container for all published versions of this package + $versionsUrl = "${baseAddress}$($id.ToLower())/index.json" + try { + $result = Invoke-RestMethod -Uri $versionsUrl -Headers $headers -ErrorAction Stop + if ($version.ToLower() -in $result.versions) { + Write-Host " SKIP $id $version — already on feed" + Remove-Item $file.FullName + $removedCount++ + continue + } + } catch { + if ($_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { + # Package has never been published — keep it + } else { throw } + } + Write-Host " PUSH $id $version — new" + } + + $remaining = (Get-ChildItem -Path "${{ parameters.packageParentPath }}" -Filter "*.nupkg" -Recurse).Count + Write-Host "Removed $removedCount already-published package(s). $remaining package(s) to push." + Write-Host "##vso[task.setvariable variable=HasNewPackages]$($remaining -gt 0)" + displayName: Remove already-published packages + +- script: dir /S "${{ parameters.packageParentPath }}" + displayName: Show NuGet packages after cleanup + +- task: 1ES.PublishNuGet@1 + displayName: 'NuGet push to ${{ parameters.feedDisplayName }}' + condition: and(succeeded(), eq(variables['HasNewPackages'], 'True')) + inputs: + useDotNetTask: true + packageParentPath: '${{ parameters.packageParentPath }}' + packagesToPush: '${{ parameters.packagesToPush }}' + nuGetFeedType: external + publishFeedCredentials: '${{ parameters.publishFeedCredentials }}' + externalEndpoint: '${{ parameters.publishFeedCredentials }}' + publishPackageMetadata: true diff --git a/.ado/templates/react-native-init-windows.yml b/.ado/templates/react-native-init-windows.yml index 878df865054..14419878b0f 100644 --- a/.ado/templates/react-native-init-windows.yml +++ b/.ado/templates/react-native-init-windows.yml @@ -19,21 +19,14 @@ parameters: - name: additionalInitArguments type: string default: '' - - name: useNuGet - type: boolean - default: false - name: useExperimentalWinUI3 type: boolean default: false - - name: publishNuGet - type: boolean - default: false - name: buildEnvironment type: string default: PullRequest - values: + values: - PullRequest - - SecurePullRequest - Continuous steps: @@ -44,25 +37,12 @@ steps: parameters: buildEnvironment: ${{ parameters.buildEnvironment }} - - ${{ if eq(parameters.useNuGet, true) }}: - - template: prep-and-pack-nuget.yml - parameters: - artifactName: ReactWindows - npmVersion: $(npmVersion) - packMicrosoftReactNative: true - packMicrosoftReactNativeCxx: true - slices: - - platform: ${{ parameters.platform }} - configuration: Release - - - ${{ if eq(parameters.publishNuGet, true) }}: - - task: PublishPipelineArtifact@1 - displayName: "Publish Artifact: CliInitNuGet.${{parameters.platform}}.${{parameters.configuration}}" - # Do nothing if the artifact was already published. E.g. after rerunning a past successful job attempt - continueOnError: true - inputs: - artifactName: CliInitNuGet.${{parameters.platform}}.${{parameters.configuration}} - targetPath: $(System.DefaultWorkingDirectory)/NugetRootFinal + # Download the final NuGet packages produced by the RNWNuget job + - task: DownloadPipelineArtifact@2 + displayName: 'Download final NuGet packages' + inputs: + artifact: ReactWindows-final-nuget + path: $(System.DefaultWorkingDirectory)\NugetTestFeed - ${{ if endsWith(parameters.template, '-app') }}: - script: | @@ -88,7 +68,7 @@ steps: ${{ else }}: workingDir: $(Agent.BuildDirectory)\testcli\windows - - powershell: | + - pwsh: | $path = (Get-ChildItem -Filter "Package.appxmanifest" -File -Recurse).FullName; [xml] $manifest = Get-Content $path $manifest.Package.Identity.Name = 'ReactNative.InitTest' @@ -107,6 +87,7 @@ steps: inputs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Start-Tracing.ps1 + pwsh: true - template: react-native-debug-info.yml parameters: @@ -115,14 +96,13 @@ steps: ${{ else }}: workingDirectory: $(Agent.BuildDirectory)\testcli - - ${{ if eq(parameters.useNuGet, true) }}: - - powershell: | - nuget.exe sources add -name TestFeed -source $(System.DefaultWorkingDirectory)\NugetTestFeed - nuget.exe sources remove -name react-native - nuget.exe sources remove -name Nuget.org - nuget.exe sources add -name Nuget.org -source https://api.nuget.org/v3/index.json - displayName: Add local NuGet test feed - workingDirectory: $(Agent.BuildDirectory)\testcli + - pwsh: | + nuget.exe sources add -name TestFeed -source $(System.DefaultWorkingDirectory)\NugetTestFeed + nuget.exe sources remove -name react-native + nuget.exe sources remove -name Nuget.org + nuget.exe sources add -name Nuget.org -source https://api.nuget.org/v3/index.json + displayName: Add local NuGet test feed + workingDirectory: $(Agent.BuildDirectory)\testcli - template: ../templates/run-windows-with-certificates.yml parameters: @@ -140,6 +120,7 @@ steps: - template: upload-build-logs.yml parameters: + oneESMode: true buildLogDirectory: '$(Build.BinariesDirectory)\${{ parameters.platform }}\${{ parameters.configuration }}\BuildLogs' # Only run the following on fabric apps @@ -160,11 +141,7 @@ steps: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Stop-Tracing.ps1 arguments: -NoAnalysis -outputFolder $(Build.StagingDirectory)/Tracing + pwsh: true condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: Upload traces - inputs: - pathtoPublish: '$(Build.StagingDirectory)/Tracing' - artifactName: 'Traces - $(Agent.JobName)-$(System.JobAttempt)' - condition: succeededOrFailed() + # Traces artifact publishing moved to templateContext.outputs in cli-init-windows.yml (1ES compliance) diff --git a/.ado/templates/run-compliance-postbuild.yml b/.ado/templates/run-compliance-postbuild.yml deleted file mode 100644 index 4b6931be493..00000000000 --- a/.ado/templates/run-compliance-postbuild.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Compliance tasks to be run post build -parameters: -- name: complianceWarnOnly - displayName: Convert compliance errors to warnings - type: boolean - default: false - -steps: - # Component Governance Detection Task (https://docs.opensource.microsoft.com/tools/cg/) - # Detects open source you use and alerts you to whether it has security vulnerabilities or legal issues. - # TODO: Reconcile with existing component-governance.yml template - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: '⚖️ Component Governance Detection' - continueOnError: ${{ parameters.complianceWarnOnly }} diff --git a/.ado/templates/run-compliance-prebuild.yml b/.ado/templates/run-compliance-prebuild.yml deleted file mode 100644 index 09cd4a19874..00000000000 --- a/.ado/templates/run-compliance-prebuild.yml +++ /dev/null @@ -1,89 +0,0 @@ -# Compliance tasks to be run pre build -parameters: -- name: complianceWarnOnly - displayName: Convert compliance errors to warnings - type: boolean - default: false - -steps: - # Anti-Malware Scan Task (https://aka.ms/gdn-azdo-antimalware) - # Runs an Anti-Malware Scan on Windows agents, using Windows Defender. - - task: securedevelopmentteam.vss-secure-development-tools.build-task-antimalware.AntiMalware@4 - displayName: '⚖️ Run Anti-Malware Scan' - inputs: - InputType: 'Basic' - ScanType: 'CustomScan' - FileDirPath: $(Build.SourcesDirectory) - EnableServices: true - SupportLogOnError: false - TreatSignatureUpdateFailureAs: 'Warning' - SignatureFreshness: 'OneDay' - TreatStaleSignatureAs: 'Error' - continueOnError: ${{ parameters.complianceWarnOnly }} - - # PoliCheck Build Task (https://aka.ms/gdn-azdo-policheck) - # Scans the text of source code, comments, and content for terminology that could be sensitive for legal, cultural, or geopolitical reasons. - - task: securedevelopmentteam.vss-secure-development-tools.build-task-policheck.PoliCheck@2 - displayName: '⚖️ Run PoliCheck' - inputs: - targetType: F - targetArgument: $(Build.SourcesDirectory) - result: PoliCheck.xml - optionsFC: 1 - optionsXS: 1 - optionsHMENABLE: 0 - optionsPE: 1|2|3|4 - optionsSEV: 1|2|3|4 - optionsUEPath: $(Build.SourcesDirectory)\.ado\config\PoliCheckExclusions.xml - optionsRulesDBPath: $(Build.SourcesDirectory)\.ado\config\PoliCheckRules.mdb - continueOnError: ${{ parameters.complianceWarnOnly }} - - # CredScan Task (https://aka.ms/gdn-azdo-credscan) - # Searches through source code and build outputs for credentials left behind in the open. - - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@3 - displayName: '⚖️ Run CredScan' - inputs: - outputFormat: pre - suppressionsFile: $(Build.SourcesDirectory)\.ado\config\CredScanSuppressions.json - batchSize: 20 - debugMode: false - continueOnError: ${{ parameters.complianceWarnOnly }} - - - task: PublishSecurityAnalysisLogs@3 - displayName: 'Publish Guardian Artifacts' - inputs: - ArtifactName: CodeAnalysisLogs - ArtifactType: Container - PublishProcessedResults: false - AllTools: true - - # PostAnalysis Task (https://docs.microsoft.com/en-us/azure/security/develop/yaml-configuration#post-analysis-task) - # Breaks the build if any of the tasks failed. - - task: PostAnalysis@2 - displayName: "⚖️ Compliance Pre-Build Analysis" - inputs: - AllTools: false - AntiMalware: true - CredScan: true - PoliCheck: true - PoliCheckBreakOn: Severity4Above - ToolLogsNotFoundAction: "Error" - # TSA Configuration for automatic bug filing - TSAEnabled: true - TSAOptions: | - { - "areaPath": "OS\\Windows Client and Services\\WinPD\\SPICE\\ReactNative", - "iterationPath": "OS\\Future", - "notificationAliases": ["$(TSANotificationAliases)"], - "codebaseAdmins": ["$(TSACodebaseAdmins)"], - "bugTags": ["SDL", "Security"], - "instanceUrl": "https://dev.azure.com/microsoft", - "projectName": "OS", - "allTools": true - } - continueOnError: ${{ parameters.complianceWarnOnly }} - - # Restore unnecessary changes that were made by the compliance tasks - - script: | - git restore $(Build.SourcesDirectory)\.ado\config\PoliCheckRules.mdb - displayName: "⚖️ Compliance Pre-Build Cleanup" diff --git a/.ado/templates/run-wack.yml b/.ado/templates/run-wack.yml index fa692a3fd71..c697385e97f 100644 --- a/.ado/templates/run-wack.yml +++ b/.ado/templates/run-wack.yml @@ -5,7 +5,7 @@ parameters: type: string steps: - - powershell: .ado/scripts/TestWACK.ps1 -PackageName ${{ parameters.packageName }} -OutputDir $(Build.StagingDirectory)\WACK + - pwsh: .ado/scripts/TestWACK.ps1 -PackageName ${{ parameters.packageName }} -OutputDir $(Build.StagingDirectory)\WACK displayName: Run WACK - task: PublishBuildArtifacts@1 diff --git a/.ado/templates/run-windows-with-certificates.yml b/.ado/templates/run-windows-with-certificates.yml index 154d4d65764..c05eef784e8 100644 --- a/.ado/templates/run-windows-with-certificates.yml +++ b/.ado/templates/run-windows-with-certificates.yml @@ -4,7 +4,6 @@ parameters: default: PullRequest values: - PullRequest - - SecurePullRequest - Continuous - name: buildConfiguration type: string diff --git a/.ado/templates/set-appx-platforms.yml b/.ado/templates/set-appx-platforms.yml index 47830ade907..4a949c64f51 100644 --- a/.ado/templates/set-appx-platforms.yml +++ b/.ado/templates/set-appx-platforms.yml @@ -26,5 +26,5 @@ parameters: # un-overriden path in official tests. steps: - ${{ if and(endsWith(parameters.buildEnvironment, 'PullRequest'), eq(parameters.configuration, 'Release')) }}: - - powershell: $env:AppxBundlePlatforms = "${{ parameters.platform }}" + - pwsh: $env:AppxBundlePlatforms = "${{ parameters.platform }}" displayName: Set AppxBundlePlatforms to "${{ parameters.platform }}" diff --git a/.ado/templates/set-experimental-feature.yml b/.ado/templates/set-experimental-feature.yml index 1080b44af6c..c3c4a7fab04 100644 --- a/.ado/templates/set-experimental-feature.yml +++ b/.ado/templates/set-experimental-feature.yml @@ -7,7 +7,7 @@ parameters: type: string steps: - - powershell: | + - pwsh: | [xml] $xmlDoc = Get-Content .\ExperimentalFeatures.props # Add to new property group at the end of the file to ensure it overrides any other setting $propertyGroup = $xmlDoc.CreateElement("PropertyGroup", $xmlDoc.DocumentElement.NamespaceURI); diff --git a/.ado/templates/set-version-vars.yml b/.ado/templates/set-version-vars.yml index b0bfc5387d4..6253aef064c 100644 --- a/.ado/templates/set-version-vars.yml +++ b/.ado/templates/set-version-vars.yml @@ -5,7 +5,6 @@ parameters: default: PullRequest values: - PullRequest - - SecurePullRequest - Continuous steps: diff --git a/.ado/templates/stop-packagers.yml b/.ado/templates/stop-packagers.yml index c121c3945ac..d83421fc9bc 100644 --- a/.ado/templates/stop-packagers.yml +++ b/.ado/templates/stop-packagers.yml @@ -3,6 +3,7 @@ steps: displayName: Cleanup any packagers left over from other builds inputs: targetType: inline # filePath | inline + pwsh: true script: | echo "ports:{" echo ${env:RNTester.PackagerPort} diff --git a/.ado/templates/upload-build-logs.yml b/.ado/templates/upload-build-logs.yml index a22b33bd207..0c4f041f3da 100644 --- a/.ado/templates/upload-build-logs.yml +++ b/.ado/templates/upload-build-logs.yml @@ -19,6 +19,7 @@ steps: condition: succeededOrFailed() inputs: targetType: inline # filePath | inline + pwsh: true script: | Write-Host "Looking for procdump64 processes..."; Get-Process | ForEach-Object { @@ -39,6 +40,7 @@ steps: condition: and(succeededOrFailed(), ${{ parameters.uploadCrashDumps }}) inputs: targetType: inline # filePath | inline + pwsh: true script: | Write-Host "Looking for crash dumps in $(CrashDumpRootPath)..."; Get-ChildItem -Path $(CrashDumpRootPath) -File -Recurse; diff --git a/.ado/templates/verdaccio-start.yml b/.ado/templates/verdaccio-start.yml index 54a91c75890..3346004a126 100644 --- a/.ado/templates/verdaccio-start.yml +++ b/.ado/templates/verdaccio-start.yml @@ -5,7 +5,7 @@ parameters: default: true steps: - - powershell: start-process verdaccio.cmd -ArgumentList @('--config', './.ado/verdaccio/config.yaml') + - pwsh: start-process verdaccio.cmd -ArgumentList @('--config', './.ado/verdaccio/config.yaml') displayName: Launch test npm server (verdaccio) - script: node .ado/scripts/waitForVerdaccio.js @@ -17,8 +17,10 @@ steps: - template: compute-beachball-branch-name.yml - ${{ if eq(parameters.beachballPublish, true) }}: - - script: npx beachball bump --branch origin/$(BeachBallBranchName) --no-push --yes --verbose --changehint "Run `yarn change` from root of repo to generate a change file." + - pwsh: npx beachball bump --branch "origin/$env:BEACHBALL_BRANCH" --no-push --yes --verbose --changehint "Run 'yarn change' from root of repo to generate a change file." displayName: Beachball bump versions + env: + BEACHBALL_BRANCH: $(BeachBallBranchName) - script: node .ado/scripts/npmPack.js --clean --no-color displayName: Pack all workspace packages diff --git a/.ado/templates/verdaccio-stop.yml b/.ado/templates/verdaccio-stop.yml index 7113dd12ea2..17e4107396a 100644 --- a/.ado/templates/verdaccio-stop.yml +++ b/.ado/templates/verdaccio-stop.yml @@ -10,15 +10,7 @@ steps: displayName: Kill Verdaccio condition: succeededOrFailed() - - ${{ if eq(parameters.uploadLogs, true) }}: - # We are experiencing random package restore failures. - # We want to uploading the vedaccio logs to aid in diagnosing if it is verdaccio or npmjs.org - - task: PublishPipelineArtifact@1 - displayName: Upload Verdaccio.log (on failure) - inputs: - targetPath: 'verdaccio.log' - artifact: '$(Agent.JobName).Verdaccio.log-$(System.JobAttempt)' - condition: failed() + # Verdaccio log artifact publishing moved to templateContext.outputs in cli-init-windows.yml (1ES compliance) - script: | call npm config delete registry diff --git a/.ado/templates/write-certificate.yml b/.ado/templates/write-certificate.yml index 271272f1ffd..88d770b0569 100644 --- a/.ado/templates/write-certificate.yml +++ b/.ado/templates/write-certificate.yml @@ -4,7 +4,7 @@ parameters: default: 'pwd' steps: - - powershell: | + - pwsh: | $certStoreRoot="cert:\CurrentUser\My" $rootFolder="$(Build.SourcesDirectory)" diff --git a/.ado/variables/shared.yml b/.ado/variables/shared.yml index 3a0c8781064..5af127d07a3 100644 --- a/.ado/variables/shared.yml +++ b/.ado/variables/shared.yml @@ -1,10 +1,4 @@ variables: - # Task auto-injection configuration - Codeql.SkipTaskAutoInjection: true - NugetSecurityAnalysisWarningLevel: 'warn' - runCodesignValidationInjection: false - skipComponentGovernanceDetection: true - # Enables `chalk` to show colored output to Azure Pipelines FORCE_COLOR: 3 diff --git a/.ado/windows-vs-pr-secure.yml b/.ado/windows-vs-pr-secure.yml deleted file mode 100644 index 3601bce96c8..00000000000 --- a/.ado/windows-vs-pr-secure.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: $(Date:yyyyMMdd).$(Rev:r) - -trigger: none # will disable CI builds entirely - -pr: - - main - - master - - "*-stable" - -variables: - - group: platform-override-zero-permission-token - -parameters: - - name: AgentPool - type: object - default: - Small: - name: rnw-pool-2 - demands: ImageOverride -equals rnw-img-vs2022-node22 - Medium: - name: rnw-pool-4 - demands: ImageOverride -equals rnw-img-vs2022-node22 - Large: - name: rnw-pool-8 - demands: ImageOverride -equals rnw-img-vs2022-node22 - -stages: - - template: stages.yml - parameters: - buildEnvironment: SecurePullRequest - AgentPool: ${{ parameters.AgentPool }} diff --git a/.ado/windows-vs-pr.yml b/.ado/windows-vs-pr.yml index ae79db81131..c97e1695768 100644 --- a/.ado/windows-vs-pr.yml +++ b/.ado/windows-vs-pr.yml @@ -1,6 +1,13 @@ +# +# The PR pipeline entry point. +# This file is a copy of pr-pipeline.yml kept for backward compatibility +# with *-stable branches that haven't been updated yet. +# Once all branches use pr-pipeline.yml, this file can be deleted. +# + name: $(Date:yyyyMMdd).$(Rev:r) -trigger: none # will disable CI builds entirely +trigger: none pr: - main @@ -10,22 +17,14 @@ pr: variables: - group: platform-override-zero-permission-token -parameters: - - name: AgentPool - type: object - default: - Small: - name: rnw-pool-2 - demands: ImageOverride -equals rnw-img-vs2022-node22 +extends: + template: build-template.yml@self + parameters: + buildEnvironment: PullRequest + AgentPool: Medium: name: rnw-pool-4 demands: ImageOverride -equals rnw-img-vs2022-node22 Large: name: rnw-pool-8 demands: ImageOverride -equals rnw-img-vs2022-node22 - -stages: - - template: stages.yml - parameters: - buildEnvironment: PullRequest - AgentPool: ${{ parameters.AgentPool }} diff --git a/.config/1espt/PipelineAutobaseliningConfig.yml b/.config/1espt/PipelineAutobaseliningConfig.yml new file mode 100644 index 00000000000..c1296a81ddd --- /dev/null +++ b/.config/1espt/PipelineAutobaseliningConfig.yml @@ -0,0 +1,37 @@ +## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. + +pipelines: + 163759: + retail: + source: + eslint: + lastModifiedDate: 2026-03-07 + psscriptanalyzer: + lastModifiedDate: 2026-03-07 + armory: + lastModifiedDate: 2026-03-07 + 63081: + retail: + source: + eslint: + lastModifiedDate: 2026-03-18 + psscriptanalyzer: + lastModifiedDate: 2026-03-18 + armory: + lastModifiedDate: 2026-03-18 + binary: + binskim: + lastModifiedDate: 2026-03-18 + 210: + usedNonDefaultBranch: true + retail: + source: + eslint: + lastModifiedDate: 2026-03-26 + armory: + lastModifiedDate: 2026-03-26 + accessibilityinsights: + lastModifiedDate: 2026-03-26 + binary: + binskim: + lastModifiedDate: 2026-03-26 diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines new file mode 100644 index 00000000000..998fd5636d0 --- /dev/null +++ b/.config/guardian/.gdnbaselines @@ -0,0 +1,351 @@ +{ + "properties": { + "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines" + }, + "version": "1.0.0", + "baselines": { + "default": { + "name": "default", + "createdDate": "2026-03-26 07:10:52Z", + "lastUpdatedDate": "2026-03-26 07:12:40Z" + } + }, + "results": { + "f2ed0cf54f65c4dc153da94f09ac17c9a29b37c58c8fabe3cd3d4e8a4cc2dc16": { + "signature": "f2ed0cf54f65c4dc153da94f09ac17c9a29b37c58c8fabe3cd3d4e8a4cc2dc16", + "alternativeSignatures": [ + "aec58dd9641bbd4c9e2fda58e680240eacd6b01346dce2b12644e45cc0510969" + ], + "target": "vnext/target/x64/Debug/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:10:52Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "2e81ed89971c8e8718d0f1260b70b1ea8338346e9d0ef555bd6104845117cc69": { + "signature": "2e81ed89971c8e8718d0f1260b70b1ea8338346e9d0ef555bd6104845117cc69", + "alternativeSignatures": [ + "ed449a197a9c0cac5018cf9e48a73538b53e2f879fe28117bd225877a1eedc0c" + ], + "target": "vnext/target/x64/Release/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:12:22Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "afcc5a5fb5c3b889ddc8040e662bf6a9ca31df2515517690120dbb8a8819ced6": { + "signature": "afcc5a5fb5c3b889ddc8040e662bf6a9ca31df2515517690120dbb8a8819ced6", + "alternativeSignatures": [ + "4af33c2febcd2b179607781f394796d016122444156a7ba173a3ceeee746a739" + ], + "target": "vnext/target/ARM64/Debug/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:07:11Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "2a53424cb64846094a502e30a7677abf0a9e2939e652063250e5b482a2569b7d": { + "signature": "2a53424cb64846094a502e30a7677abf0a9e2939e652063250e5b482a2569b7d", + "alternativeSignatures": [ + "d9c3aa07f755d3dff5e02302bf01b7468fa367a4f6b5c35d0bd849e804a40814" + ], + "target": "vnext/target/ARM64EC/Release/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:10:21Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "688803d8f1e3d282e10db37e811cc47fed7d8578624b1c1e53e894888abdc66c": { + "signature": "688803d8f1e3d282e10db37e811cc47fed7d8578624b1c1e53e894888abdc66c", + "alternativeSignatures": [ + "14980d9e3b960faa8e3a6c7381fc8d55cb1cf1c631b4b0285dc3789a2d686566" + ], + "target": "NugetRoot/Desktop/ARM64EC/Debug/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "63fcc3a0ca8566a931681a8a8c1f28baf49f6c201bf842e7cf5579ff5e691cf3": { + "signature": "63fcc3a0ca8566a931681a8a8c1f28baf49f6c201bf842e7cf5579ff5e691cf3", + "alternativeSignatures": [ + "e602a6306d1b4666ce2ef75d2f0fbbd348d1d4194fde8aa2b3325074bf5655bf" + ], + "target": "NugetRoot/Desktop/ARM64EC/Release/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "e33fcf182251f26aafe7858d391d38b1a2d896db3bded7f8347fc9486624a955": { + "signature": "e33fcf182251f26aafe7858d391d38b1a2d896db3bded7f8347fc9486624a955", + "alternativeSignatures": [ + "b9dd9bfdb21a69f967e510fd436accefe25d310ce2fa40d300894db7a6a64897" + ], + "target": "NugetRoot/Desktop/x64/Debug/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "022d2235552edce91cc2636acb3117aaa2ccdbe556d1fe7a8d1899fef1c96977": { + "signature": "022d2235552edce91cc2636acb3117aaa2ccdbe556d1fe7a8d1899fef1c96977", + "alternativeSignatures": [ + "4536b55c0755ae03021209ca81e576574bb386770f25de5433223bcbb5b102de" + ], + "target": "NugetRoot/Desktop/x64/Release/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "abb79292d0b4b5e3196ebe22fd519656e8642da4ecd70699844866b66ed4dcc7": { + "signature": "abb79292d0b4b5e3196ebe22fd519656e8642da4ecd70699844866b66ed4dcc7", + "alternativeSignatures": [ + "ee1f48cc9e95922c970647f837f9c02f1c30bdaa063f92dbae6102bb7429bac0" + ], + "target": "NugetRoot/Desktop/x86/Debug/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "1f56b406b22607dea43183e407ba810237e90ccdc0bb329ff00b841d128e63a1": { + "signature": "1f56b406b22607dea43183e407ba810237e90ccdc0bb329ff00b841d128e63a1", + "alternativeSignatures": [ + "c555d3a3583cdcb11545681db95e7db69e708374a66c31e51cb8a12632ba3766" + ], + "target": "NugetRoot/Desktop/x86/Release/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "c2c90e9743b86f58606824a6db14290f8241182568c833333a67b7e87101a9f9": { + "signature": "c2c90e9743b86f58606824a6db14290f8241182568c833333a67b7e87101a9f9", + "alternativeSignatures": [ + "5885bc2070e63454f470f0a581aedd8b933172228ac8c1699978d94df648c9ce" + ], + "target": "NugetRoot/ReactWindows/ARM64/Release/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "ed706c480e5d6b53b62981fe65524cfe96f072a3d2acb79ba261d67c016e3ea1": { + "signature": "ed706c480e5d6b53b62981fe65524cfe96f072a3d2acb79ba261d67c016e3ea1", + "alternativeSignatures": [ + "f4137c3cb697b844e9d3d8867320b64fef46f79f42afaa92cba399eb0e41fb62" + ], + "target": "NugetRoot/ReactWindows/x64/Release/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "a6cf7ffe274a0779d8aa9b6e649e0e9a344f90a9d7f7be3e4cb2c2f3f447f761": { + "signature": "a6cf7ffe274a0779d8aa9b6e649e0e9a344f90a9d7f7be3e4cb2c2f3f447f761", + "alternativeSignatures": [ + "87c4b69c24e40c69d6168becb0775eee763917ef5bd6d3b3d679faf38024bd1d" + ], + "target": "NugetRoot/ReactWindows/x86/Release/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:36:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "572b50a06b25e58736323f94d174b088e425947d5870ce5ae518f83b528c6e2d": { + "signature": "572b50a06b25e58736323f94d174b088e425947d5870ce5ae518f83b528c6e2d", + "alternativeSignatures": [ + "fa14adc368febe268c4e119a5608096df286cd308fa1aeae24ffdae709eea68e" + ], + "target": "vnext/target/x86/Release/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:14:09Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "7c8e18bfbf20d73615d077906c1cb3181a9129b87db57994a70526e0b33d7e71": { + "signature": "7c8e18bfbf20d73615d077906c1cb3181a9129b87db57994a70526e0b33d7e71", + "alternativeSignatures": [ + "ab9379eb223688f7841a6f891f8c0661de05fff65781fa876f2011c51aa5cece" + ], + "target": "vnext/target/x86/Debug/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:11:05Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "25bba357018db7971bcd7b5a166daa780091438df91582a138657338babbee95": { + "signature": "25bba357018db7971bcd7b5a166daa780091438df91582a138657338babbee95", + "alternativeSignatures": [ + "1fa28a88246da4815a8c60246c0970e921f0d2a4c83630ac6909c0ac6015df5c" + ], + "target": "vnext/target/ARM64/Release/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:08:29Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "51a105fd20e702818fc3457105ab35b954120c912d56a272a78b6c9d7949d95f": { + "signature": "51a105fd20e702818fc3457105ab35b954120c912d56a272a78b6c9d7949d95f", + "alternativeSignatures": [ + "48d93ff74cd6084a25d58b6916db64f7f376f580544a83f8e30b68f34edd2ab3" + ], + "target": "vnext/target/x86/Release/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:12:27Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "5522f1c27211ae48014df348c9ee2ef4efff347d03f97716bf19b7e2892c3ba9": { + "signature": "5522f1c27211ae48014df348c9ee2ef4efff347d03f97716bf19b7e2892c3ba9", + "alternativeSignatures": [ + "2e6396a0961651ac2077ac704557a2bb1340144651a18eb64dda0309b81d4934" + ], + "target": "vnext/target/x86/Debug/Microsoft.ReactNative/Microsoft.ReactNative.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:12:50Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "735563a527f27ead8cc9feab0c7d0d4caa1a618acd5d5b3bb3d23e87363ebda7": { + "signature": "735563a527f27ead8cc9feab0c7d0d4caa1a618acd5d5b3bb3d23e87363ebda7", + "alternativeSignatures": [ + "0c8ed58595d218fce462d61c4835b2c169d817aff1128482ba05f20c3e9782ce" + ], + "target": "vnext/target/ARM64EC/Debug/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:08:06Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "55fc11d7c2fdb88bd33b1d1b712800586de5466b6180bee30c1b97e3bc7f8de6": { + "signature": "55fc11d7c2fdb88bd33b1d1b712800586de5466b6180bee30c1b97e3bc7f8de6", + "alternativeSignatures": [ + "bcd6e88dd528c144d6efeda13170017fd5c451e38945069e0c683347104c9431" + ], + "target": "vnext/target/x64/Release/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:15:40Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + }, + "f361a328ed2845ecea35845e2aa765efd85c5cfffe99e4658d6eb504eefc7aa1": { + "signature": "f361a328ed2845ecea35845e2aa765efd85c5cfffe99e4658d6eb504eefc7aa1", + "alternativeSignatures": [ + "acb74c0badf79a8bc5fd0fc2d3c7ce285ba732fca56134f778e0da8e3f988569" + ], + "target": "vnext/target/x64/Debug/React.Windows.Desktop.DLL/react-native-win32.dll", + "uriBaseId": "file:///D:/a/_work/1/s/", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-03-26 07:10:59Z", + "expirationDate": "2026-09-12 08:00:06Z", + "justification": "This error is baselined with an expiration date of 180 days from 2026-03-26 08:00:06Z" + } + } +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 16a2e93d929..3bfbc0e4346 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,6 +21,16 @@ updates: update-types: - 'minor' - 'patch' + other-dependencies: + patterns: + - '*' + exclude-patterns: + - '@babel*' + - '@jest*' + - '@rnx-kit*' + update-types: + - 'minor' + - 'patch' schedule: interval: daily time: "05:00" diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 2eaca728a85..12f59925618 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -29,3 +29,17 @@ Cherry-picks a specific commit into a target branch. - The workflow will fail if there are merge conflicts during cherry-pick - If conflicts occur, you'll need to resolve them manually - The commit will be pushed directly to the target branch after successful cherry-pick +### Trigger AZP on Dependabot PRs + +**File:** `dependabot-azp-trigger.yml` + +Automatically comments `/azp run PR` on pull requests opened or updated by Dependabot, triggering Azure Pipelines CI without manual intervention. The comment is posted using rnbot's PAT (`RNBOT_GITHUB_PAT`) so AZP recognizes the trigger. + +**Trigger:** Runs on `pull_request_target` events (`opened`, `synchronize`, `reopened`) when the PR author is `dependabot[bot]`. + +**Setup:** Requires a `RNBOT_GITHUB_PAT` GitHub Actions secret containing rnbot's personal access token with `repo` scope. + +**Notes:** + +- Checks `github.event.pull_request.user.login` (not `github.actor`) so it still triggers when a maintainer clicks "Update branch" +- Fires on `opened`, `synchronize` (new pushes/rebases), and `reopened` events \ No newline at end of file diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml deleted file mode 100644 index 1c9021ab8ef..00000000000 --- a/.github/workflows/cherry-pick.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Cherry Pick Backports - -on: - pull_request: - types: [labeled] - -permissions: - contents: write - pull-requests: write - -jobs: - cherry-pick: - runs-on: ubuntu-latest - - steps: - - name: Check merged PR and extract target branch - id: extract - shell: bash - run: | - # Run only for merged PRs - if [ "${{ github.event.pull_request.merged }}" != "true" ]; then - echo "PR not merged. Exiting." - exit 0 - fi - - # Extract cherry-pick/ label - LABELS_JSON='${{ toJson(github.event.pull_request.labels) }}' - LABEL=$(echo "$LABELS_JSON" | jq -r '.[] | select(.name | startswith("cherry-pick/")) | .name' | head -n 1) - - if [ -z "$LABEL" ] || [ "$LABEL" = "null" ]; then - echo "No cherry-pick label found. Exiting." - exit 0 - fi - - TARGET_BRANCH="${LABEL#cherry-pick/}" - echo "target=$TARGET_BRANCH" >> "$GITHUB_OUTPUT" - - - name: Checkout repository - if: steps.extract.outputs.target != '' - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Configure git - if: steps.extract.outputs.target != '' - run: | - git config user.name "github-actions" - git config user.email "github-actions@github.com" - - - name: Cherry-pick merge commit (allow conflicts) - if: steps.extract.outputs.target != '' - run: | - PR_NUMBER=${{ github.event.pull_request.number }} - TARGET_BRANCH=${{ steps.extract.outputs.target }} - NEW_BRANCH=cherry-pick/pr-${PR_NUMBER}-to-${TARGET_BRANCH} - - # Fetch target branch - git fetch origin ${TARGET_BRANCH} - - # Create working branch from target - git checkout -b ${NEW_BRANCH} origin/${TARGET_BRANCH} - - # Cherry-pick merge commit - # -m 1 = take PR side - # || true = allow conflicts - git cherry-pick -m 1 ${{ github.event.pull_request.merge_commit_sha }} || true - - git status - - # Push branch even if conflicts exist - git push --force-with-lease origin ${NEW_BRANCH} - - - name: Create backport PR - if: steps.extract.outputs.target != '' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr create \ - --base ${{ steps.extract.outputs.target }} \ - --head cherry-pick/pr-${{ github.event.pull_request.number }}-to-${{ steps.extract.outputs.target }} \ - --title "Cherry-pick PR #${{ github.event.pull_request.number }} to ${{ steps.extract.outputs.target }}" \ - --body "Automated backport. Resolve conflicts if present." \ No newline at end of file diff --git a/.github/workflows/cleanup-stale-branches.yml b/.github/workflows/cleanup-stale-branches.yml new file mode 100644 index 00000000000..db3287c879c --- /dev/null +++ b/.github/workflows/cleanup-stale-branches.yml @@ -0,0 +1,67 @@ +name: Cleanup Stale Branches + +on: + schedule: + - cron: '0 8 * * 1' # Every Monday at 08:00 UTC + workflow_dispatch: + inputs: + dry_run: + description: 'List stale branches without deleting' + type: boolean + default: true + +permissions: + contents: write + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Delete stale branches + # To preserve your branch from cleanup, do ONE of: + # 1. Rename it under archive/: git branch -m my-branch archive/my-branch + # 2. Keep an open PR (including drafts) pointing to it + # 3. Add the branch pattern to PROTECTED below + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + DRY_RUN="${{ github.event_name == 'schedule' && 'true' || inputs.dry_run }}" + COPILOT_DAYS=90 + DEFAULT_DAYS=180 + COPILOT_CUTOFF=$(date -d "$COPILOT_DAYS days ago" +%s) + DEFAULT_CUTOFF=$(date -d "$DEFAULT_DAYS days ago" +%s) + PROTECTED='^(main|master|develop|release/|hotfix/|archive/|[0-9]+\.[0-9]+-stable|preview-)' + + echo "Stale threshold: copilot/* = $COPILOT_DAYS days, others = $DEFAULT_DAYS days | Dry run: $DRY_RUN" + + git branch -r --format='%(refname:short) %(committerdate:unix)' | while read -r REF DATE; do + BRANCH="${REF#origin/}" + [ "$BRANCH" = "HEAD" ] && continue + echo "$BRANCH" | grep -qE "$PROTECTED" && continue + + # copilot branches: 90 days, all others: 180 days + if echo "$BRANCH" | grep -q '^copilot'; then + [ "$DATE" -ge "$COPILOT_CUTOFF" ] 2>/dev/null && continue + else + [ "$DATE" -ge "$DEFAULT_CUTOFF" ] 2>/dev/null && continue + fi + + # Skip branches with open PRs (including drafts) + PR_COUNT=$(gh pr list --head "$BRANCH" --state open --json number --jq 'length' 2>/dev/null || echo "0") + if [ "$PR_COUNT" -gt 0 ]; then + echo "[skipped] $BRANCH (has open PR)" + continue + fi + + LAST=$(date -d "@$DATE" +%Y-%m-%d) + if [ "$DRY_RUN" = "true" ]; then + echo "[stale] $BRANCH (last commit: $LAST)" + else + echo "Deleting $BRANCH (last commit: $LAST)" + git push origin --delete "$BRANCH" || echo " Failed to delete $BRANCH" + fi + done diff --git a/.github/workflows/dependabot-azp-trigger.yml b/.github/workflows/dependabot-azp-trigger.yml new file mode 100644 index 00000000000..0c4072a6bf1 --- /dev/null +++ b/.github/workflows/dependabot-azp-trigger.yml @@ -0,0 +1,18 @@ +name: Trigger AZP on Dependabot PRs + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + trigger-azp: + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Comment /azp run PR + env: + GH_TOKEN: ${{ secrets.RNBOT_GITHUB_PAT }} + run: | + gh pr comment "${{ github.event.pull_request.number }}" \ + --repo "${{ github.repository }}" \ + --body "/azp run PR" diff --git a/.github/workflows/perf-comment.yml b/.github/workflows/perf-comment.yml new file mode 100644 index 00000000000..b8c05138fd8 --- /dev/null +++ b/.github/workflows/perf-comment.yml @@ -0,0 +1,94 @@ +name: Post Perf Results to PR + +# Runs after the Perf Tests workflow completes. Uses workflow_run so +# the GITHUB_TOKEN is scoped to the base repo and can write PR comments +# even when the PR comes from a fork. +on: + workflow_run: + workflows: ['Perf Tests'] + types: [completed] + +jobs: + comment: + name: Post PR Comment + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion != 'cancelled' + + permissions: + pull-requests: write + actions: read + + steps: + - name: Download perf results artifact + uses: actions/download-artifact@v4 + with: + name: perf-results + path: perf-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Read PR number + id: pr + run: | + if [ ! -f perf-results/pr-number.txt ]; then + echo "No PR number file found — skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + PR_NUMBER=$(cat perf-results/pr-number.txt | tr -d '[:space:]') + echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Read markdown report + if: steps.pr.outputs.skip != 'true' + id: report + run: | + if [ ! -f perf-results/report.md ]; then + echo "No report.md found — skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Post or update PR comment + if: steps.pr.outputs.skip != 'true' && steps.report.outputs.skip != 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const marker = ''; + const reportPath = 'perf-results/report.md'; + const prNumber = parseInt('${{ steps.pr.outputs.number }}', 10); + + const markdown = fs.readFileSync(reportPath, 'utf-8'); + const body = `${marker}\n${markdown}`; + + // Find existing comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + }); + + const existing = comments.find(c => c.body && c.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + core.info(`Updated existing comment (id: ${existing.id})`); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + core.info('Created new comment'); + } diff --git a/.github/workflows/perf-tests.yml b/.github/workflows/perf-tests.yml new file mode 100644 index 00000000000..d29a71a3290 --- /dev/null +++ b/.github/workflows/perf-tests.yml @@ -0,0 +1,91 @@ +name: Perf Tests + +# Triggers on PRs touching source that could affect perf, +# and on pushes to main/stable for baseline updates. +on: + pull_request: + branches: [main, '*-stable'] + paths: + - 'vnext/**' + - 'packages/**' + - 'vnext/Scripts/perf/**' + - '.github/workflows/perf-tests.yml' + push: + branches: [main] + paths: + - 'packages/e2e-test-app-fabric/test/__perf__/**' + + # Allow manual trigger for debugging + workflow_dispatch: + +# Cancel in-progress runs for the same PR +concurrency: + group: perf-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + perf-tests: + name: Component Performance Tests + runs-on: windows-latest + timeout-minutes: 30 + + permissions: + contents: read + actions: read + + steps: + # ── Setup ────────────────────────────────────────────── + - name: Checkout head (PR) + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Need history for baseline comparison + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build perf-testing package + run: yarn workspace @react-native-windows/perf-testing build + + # ── Run Tests ────────────────────────────────────────── + - name: Run perf tests + id: perf-run + working-directory: packages/e2e-test-app-fabric + env: + CI: 'true' + RN_TARGET_PLATFORM: windows + run: yarn perf:ci + continue-on-error: true # Don't fail here — let comparison decide + + # ── Compare & Report ─────────────────────────────────── + - name: Compare against baselines + id: compare + working-directory: packages/e2e-test-app-fabric + run: yarn perf:ci:compare + continue-on-error: true + + - name: Save PR number + if: github.event_name == 'pull_request' + run: echo "${{ github.event.pull_request.number }}" > packages/e2e-test-app-fabric/.perf-results/pr-number.txt + + - name: Upload perf results + if: always() + uses: actions/upload-artifact@v4 + with: + name: perf-results + path: | + packages/e2e-test-app-fabric/.perf-results/ + packages/e2e-test-app-fabric/test/__perf__/**/__perf_snapshots__/ + retention-days: 30 + + # ── Status Gate ──────────────────────────────────────── + - name: Check for regressions + if: steps.compare.outcome == 'failure' + run: | + echo "::error::Performance regressions detected. See PR comment for details." + exit 1 diff --git a/.gitignore b/.gitignore index 94a413d154c..8fa91c73fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,7 @@ Directory.Build.local.targets *.userprefs #Tooling +tools/ _ReSharper*/ *.resharper [Tt]est[Rr]esult* @@ -196,3 +197,4 @@ nul .store*/* /npm-pkgs +/.claude/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cb460fd6a3..d55544888b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,16 +123,6 @@ Build and see if your solution works. Consult [Building React-Native-Windows](ht Testing is a key component in the development workflow. If your changes affect existing test cases, or you're working on brand new features and also the accompanying test cases, see [End-to-End Testing](https://github.com/microsoft/react-native-windows/blob/main/docs/e2e-testing.md) for more information about how to validate your work locally. -### 🛡️ Security & Compliance - -React Native Windows follows Microsoft's Secure Development Lifecycle (SDL) requirements. As part of this commitment: - -- **Attack Surface Analyzer (ASA)** runs automatically on every PR to detect security regressions during the build process. This ensures that builds don't inadvertently weaken the OS security configuration. -- If ASA detects changes in your PR, review the artifacts in the PR build to understand the security implications. -- For more information about ASA, see [Attack Surface Analyzer documentation](https://github.com/microsoft/react-native-windows/blob/main/docs/attack-surface-analyzer.md). - -Most changes won't trigger ASA findings, but if yours does, the documentation explains how to review and address the findings. - ### ✅ Code Review When you'd like the team to review your PR, (even if the work is not yet fully-complete), open a PR so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge. diff --git a/Directory.Build.targets b/Directory.Build.targets index cc00330b206..a8b078a0544 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -105,6 +105,8 @@ BaseOutDir; ReactNativeDir; ReactNativeWindowsDir; + RnwNewArch; + UseFabric; FollyDir; YogaDir; WinVer; @@ -120,7 +122,6 @@ HermesVersion; HermesPackage; HermesArch; - UseFabric; UseV8; V8Package; V8Version; diff --git a/README.md b/README.md index fca6bff31ad..ba73b3047b8 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ React Native has [great documentation](https://reactnative.dev/docs/getting-star - Check the [samples repo](https://github.com/microsoft/react-native-windows-samples) for more standalone samples. - The [React Native Gallery](https://github.com/microsoft/react-native-gallery) app demonstrates various components in an interactive way. - Check out the [React Native Developer Blog](https://devblogs.microsoft.com/react-native/) to see examples from past conference talks, blog posts, and more. -- For more sample code browse the [RNTester folder](https://github.com/microsoft/react-native-windows/tree/main/packages/e2e-test-app-fabric/windows/RNTesterApp) in the GitHub web UI. +- For more sample code browse the [RNTester folder](https://github.com/microsoft/react-native-windows/tree/main/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric) in the GitHub web UI. ## 📢 Contributing See [Contributing guidelines](https://github.com/microsoft/react-native-windows/blob/main/CONTRIBUTING.md) for how to setup your fork of the repo and start a PR to contribute to React Native for Windows. diff --git a/docs/attack-surface-analyzer.md b/docs/attack-surface-analyzer.md deleted file mode 100644 index 92bd7af1727..00000000000 --- a/docs/attack-surface-analyzer.md +++ /dev/null @@ -1,207 +0,0 @@ -# Attack Surface Analyzer (ASA) Integration - -## Overview - -Attack Surface Analyzer (ASA) is integrated into the React Native Windows CI pipeline to meet **Secure Development Lifecycle (SDL)** compliance requirements. ASA validates that installers or other high-privilege programs do not weaken the security configuration of the operating system. - -## What is ASA? - -When installing software on an operating system, elevated privileges are often required. Since installers typically run with 'Administrator' or 'root' privileges, they can easily change the security configuration of the operating system, potentially leaving it in a weakened state after installation is complete. - -Attack Surface Analyzer is a Microsoft tool that helps determine the changes made to an operating system during software installation by: -1. Taking a "before" snapshot of the system state -2. Performing the installation or build process -3. Taking an "after" snapshot of the system state -4. Comparing the two snapshots to identify security-relevant changes - -## How ASA Runs in CI - -ASA is integrated into the Build stage of the PR pipeline and runs automatically on secure pull requests (SecurePullRequest builds). The job performs the following steps: - -### 1. Installation -```yaml -dotnet tool install --global Microsoft.CST.AttackSurfaceAnalyzer.CLI -``` - -### 2. Before Snapshot -Captures the initial system state before any build operations: -```bash -asa collect -r before --verbose -``` - -### 3. Build Process -Builds the React Native Windows solution (simulating package installation): -- Compiles Microsoft.ReactNative.sln (x64, Release) -- This step represents the "installation" that ASA analyzes - -### 4. After Snapshot -Captures the system state after build operations: -```bash -asa collect -r after --verbose -``` - -### 5. Comparison & Analysis -Exports and analyzes the differences: -```bash -asa export-collect -f before after -o asa-comparison.json -asa export-collect -f before after -o asa-comparison.html -``` - -### 6. Results Publication -Results are published as build artifacts for review: -- JSON format: `asa-comparison.json` (machine-readable) -- HTML format: `asa-comparison.html` (human-readable) - -## What ASA Checks For - -ASA monitors for security-relevant changes including: - -- **Registry Changes**: Permission modifications, new keys, or value changes -- **File System Changes**: Permission modifications, new files, or attribute changes -- **Service Installations**: New services or service configuration changes -- **Firewall Rules**: New rules or modifications to existing rules -- **Certificate Store**: New certificates or modifications to certificate stores -- **User/Group Changes**: New users, groups, or permission changes -- **Port/Network Changes**: New listening ports or network configuration changes - -## Reviewing ASA Results - -### In Azure DevOps - -1. Go to the failed/completed build -2. Navigate to the "Artifacts" section -3. Download the `ASA_Results` artifact -4. Open `asa-comparison.html` in a browser for a visual overview -5. Review `asa-comparison.json` for detailed, programmatic analysis - -### Understanding Results - -**Expected Changes:** -- Build artifacts in the build directory -- Temporary files in system temp directories -- MSBuild cache updates -- Visual Studio temporary files - -**Unexpected Changes (Require Review):** -- Registry permission changes in system areas -- Service installations -- Firewall rule additions -- Certificate store modifications -- Changes to Program Files or System32 directories - -## SDL Compliance - -ASA integration satisfies the SDL requirement: - -> **Microsoft.Security.AccessControl.10011**: Use Attack Surface Analyzer (ASA) to validate that installers or other high-privilege programs do not weaken the security configuration of the operating system. - -All issues identified by ASA must be **fixed** or **justified** before merging. If ASA detects security regressions: - -1. Review the specific changes in the ASA report -2. Determine if the changes are intentional and necessary -3. If unintentional, fix the installation/build process to avoid the change -4. If intentional, document the justification in the PR -5. Security team may need to approve certain changes - -## Pipeline Configuration - -ASA runs as part of the Build stage in `.ado/stages.yml`: - -```yaml -- template: jobs/attack-surface-analyzer.yml - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - complianceWarnOnly: true -``` - -### Parameters - -- **buildEnvironment**: Currently configured to run only for SecurePullRequest builds -- **AgentPool**: Defines the build agent pool (Medium tier recommended) -- **complianceWarnOnly**: When `true`, ASA failures won't block PR (default: `true`) - -ASA is configured to run only for SecurePullRequest builds to ensure thorough security scanning in controlled environments. It uses `complianceWarnOnly: true` to allow gradual adoption. This can be changed to `false` to enforce blocking on security regressions. - -## Local Testing - -Developers can run ASA locally to test before submitting a PR: - -### Prerequisites -```powershell -# Install ASA CLI -dotnet tool install --global Microsoft.CST.AttackSurfaceAnalyzer.CLI - -# Verify installation -asa --version -``` - -### Running ASA Locally - -```powershell -# 1. Take before snapshot -asa collect -r before - -# 2. Perform your build/installation -cd vnext -msbuild Microsoft.ReactNative.sln /p:Configuration=Release /p:Platform=x64 - -# 3. Take after snapshot -asa collect -r after - -# 4. Export comparison -asa export-collect -f before after -o .\asa-comparison.html - -# 5. Review results -start .\asa-comparison.html -``` - -### GUI Mode (Alternative) - -ASA also provides a browser-based GUI: - -```powershell -asa gui -``` - -Then navigate to `http://localhost:5000` in your browser. - -## Troubleshooting - -### ASA Installation Fails - -**Issue**: `dotnet tool install` fails -**Solution**: Ensure .NET SDK 8.0 or later is installed: -```powershell -dotnet --version -``` - -### Large Number of Changes Detected - -**Issue**: ASA reports many changes -**Solution**: -- Filter out expected build artifacts -- Check if antivirus or other background processes are making changes -- Ensure a clean build environment - -### ASA Job Times Out - -**Issue**: ASA job exceeds 60-minute timeout -**Solution**: -- Check for hung processes during build -- Review snapshot collection performance -- Consider increasing timeout in `.ado/jobs/attack-surface-analyzer.yml` - -## References - -- [Microsoft Attack Surface Analyzer GitHub](https://github.com/microsoft/AttackSurfaceAnalyzer) -- [ASA Documentation](https://github.com/microsoft/AttackSurfaceAnalyzer/wiki) -- [SDL Requirements](https://liquid.microsoft.com/Web/Object/Read/MS.Security/Requirements/Microsoft.Security.AccessControl.10011) -- [ASA CLI Usage](https://github.com/microsoft/AttackSurfaceAnalyzer/wiki/Command-Line-Usage) - -## Support - -For issues or questions about ASA integration: -- Check the [ASA GitHub Issues](https://github.com/microsoft/AttackSurfaceAnalyzer/issues) -- Contact the React Native Windows security team -- Review the SDL compliance documentation in Service Tree diff --git a/docs/branch-lifecycle-policy.md b/docs/branch-lifecycle-policy.md new file mode 100644 index 00000000000..36a18409df2 --- /dev/null +++ b/docs/branch-lifecycle-policy.md @@ -0,0 +1,69 @@ +# Branch Lifecycle Policy + +This repository enforces a branch lifecycle policy to keep the branch list clean and manageable. Stale branches are identified automatically and removed after a period of inactivity. + +## How It Works + +A scheduled GitHub Action ([cleanup-stale-branches.yml](../.github/workflows/cleanup-stale-branches.yml)) runs **every Monday at 08:00 UTC** and scans all remote branches for inactivity. + +### Staleness Thresholds + +| Branch type | Stale after | Examples | +|-------------|-------------|----------| +| `copilot/*` | **90 days** | `copilot/fix-xyz`, `copilot/workspace` | +| All other branches | **180 days** | `user/feature`, `abhi/experiment` | + +Scheduled runs are always **dry run** (report only). Deletions require a manual trigger. + +## Protected Branches + +The following branch patterns are **always excluded** from cleanup, regardless of age: + +- `main` / `master` +- `develop` +- `release/*` +- `hotfix/*` +- `archive/*` +- `*-stable` (e.g., `0.72-stable`, `0.80-stable`) +- `preview-*` (e.g., `preview-0.80-test`) + +## Additional Safeguards + +- **Open PRs** — Branches with any open pull request (including drafts) are automatically skipped. + +## How to Keep a Branch + +If your branch must be retained beyond the staleness threshold, use **any** of these methods: + +### 1. Use a protected prefix +Rename your branch under `archive/`: +``` +git branch -m my-old-branch archive/my-old-branch +git push origin archive/my-old-branch +git push origin --delete my-old-branch +``` + +### 2. Keep an open PR +Create or keep a pull request (even a draft) from your branch. The workflow skips any branch with an open PR. + +### 3. Add the pattern to the workflow +Add your branch prefix to the `PROTECTED` regex in the workflow file and submit a PR. + +## Manual Cleanup + +Maintainers can trigger the workflow manually from the **Actions** tab: + +1. Go to **Actions → Cleanup Stale Branches** +2. Click **Run workflow** +3. Set `dry_run = false` to actually delete stale branches + +## FAQ + +**Q: What happens if my branch is deleted by mistake?** +A: Git branch deletions on GitHub are reversible for a short period. You can also restore from a local clone. + +**Q: Will this delete branches with open pull requests?** +A: No. The workflow checks for open PRs (including drafts) and skips those branches. + +**Q: What about stable and preview branches?** +A: All `*-stable` and `preview-*` branches are protected by default and will never be deleted. diff --git a/docs/build-pipelines.md b/docs/build-pipelines.md new file mode 100644 index 00000000000..35327b6782b --- /dev/null +++ b/docs/build-pipelines.md @@ -0,0 +1,105 @@ +# Build Pipelines + +React Native Windows uses three Azure DevOps pipelines, all built on the 1ES Pipeline Templates. + +## Pipeline Overview + +| Pipeline | File | Template | Trigger | +|----------|------|----------|---------| +| **CI** | `ci-pipeline.yml` | 1ES Official | Push to `main`, `*-stable` | +| **PR** | `pr-pipeline.yml` | 1ES Unofficial | PRs to `main`, `*-stable` | +| **Release** | `release.yml` | 1ES Official | Triggered by successful CI run | + +CI and PR share a single `build-template.yml` that contains all build, test, and packaging logic. The `buildEnvironment` parameter (`Continuous` or `PullRequest`) controls what runs: + +- **Both**: build, test, lint, NuGet pack, CLI init verification +- **CI only**: ESRP code signing, release tagging +- **PR only**: beachball change file check + +## Stages + +``` +Setup ──┬── Build ──── CLI + │ + └── Tests +``` + +### Setup + +Detects whether this is a **release build** (commit message starts with `RELEASE:`) or a **developer build**. For developer builds, runs beachball to bump versions. Publishes version variables for all downstream stages. + +### Build + +Runs in parallel for each platform/configuration in the build matrix: + +- **Desktop** (x64/x86/ARM64EC, Debug/Release) — builds, runs unit and integration tests, signs shipping binaries (CI) +- **Universal** (x64/x86/ARM64, Debug/Release) — builds, runs unit tests, signs shipping binaries (CI) +- **Linting** — JS/TS lint, format check, override validation +- **NPM Pack** — creates npm tarballs +- **NuGet Pack** — downloads all build artifacts, packs NuGet packages, ESRP signs them (CI) + +Each build job uses `*-single.yml` step templates that build the full `.sln`, run tests, and stage shipping artifacts — we test what we ship. + +### Tests + +Runs in parallel with Build (both depend only on Setup): + +- **Node Tests** — Jest-based JavaScript tests +- **Playground** — builds playground apps across configs +- **E2E Tests** — Fabric end-to-end tests + +### CLI + +Depends on Build (needs the NuGet packages). Runs `react-native init` + `react-native-windows init` against the just-built packages to verify the developer experience works. + +## Agent Pools + +| Pool | Used By | +|------|---------| +| `rnw-pool-4-microsoft` | CI: most jobs (default) | +| `rnw-pool-8-microsoft` | CI: native builds (Desktop, Universal) | +| `rnw-pool-4` | PR: most jobs (default) | +| `rnw-pool-8` | PR: native builds | + +PR uses public pools (no `-microsoft` suffix). The PR entry point overrides the pool defaults. + +## SDL & Compliance + +The 1ES Official template (CI) handles SDL automatically: + +- **CredScan** — credential scanning +- **BinSkim** — binary security analysis (per-job targets via `templateContext.sdl`) +- **Component Governance** — open-source license compliance +- **CodeQL** — static analysis +- **ESLint** — JS/TS analysis (with exclusions) + +PREfast, SpotBugs, and Bandit are disabled (no actionable findings for this repo). + +## Release Flow + +1. Developer merges a PR to `main` +2. **CI pipeline** runs: build, test, sign, pack +3. On success, a separate **prepare-release-bot** pipeline creates/updates a release PR (e.g., `prepare-release/main`) +4. When the release PR is merged (commit message: `RELEASE: ...`), CI runs again as a **release build**: + - Skips beachball bump (versions already committed) + - Builds, tests, signs, packs + - Tags the source commit (`react-native-windows_v`) +5. **Release pipeline** triggers on successful CI, publishing packages to npm and NuGet feeds + +## Key Files + +| File | Purpose | +|------|---------| +| `.ado/ci-pipeline.yml` | CI entry point | +| `.ado/pr-pipeline.yml` | PR entry point | +| `.ado/build-template.yml` | Shared build/test/pack logic | +| `.ado/release.yml` | Release pipeline (publishes to feeds) | +| `.ado/jobs/desktop-single.yml` | Desktop build + test steps | +| `.ado/jobs/universal-single.yml` | Universal build + test steps | +| `.ado/jobs/cli-init-windows.yml` | CLI init verification | +| `.ado/jobs/e2e-test.yml` | E2E test job | +| `.ado/jobs/playground.yml` | Playground build job | +| `.ado/jobs/linting.yml` | Linting job | +| `.ado/jobs/node-tests.yml` | Node/Jest test job | +| `.ado/templates/prep-and-pack-nuget.yml` | NuGet packing steps | +| `.ado/templates/esrp-codesign-*.yml` | ESRP signing steps | diff --git a/docs/managedCodeGen.md b/docs/managedCodeGen.md deleted file mode 100644 index 2f33659fbc9..00000000000 --- a/docs/managedCodeGen.md +++ /dev/null @@ -1,38 +0,0 @@ -# Code generation for managed react-native modules - -This feature is applicable to C# implemented modules or `ViewManager`s. Its objective is to ensure the code needed to integrate with the `react-native` application is constructed at build time instead of at runtime. - -In earlier versions of react-native-windows managed modules registered themselves based on reflection at runtime. This method has a few disadvantages: -1) the error handling was limited and only available at runtime. -2) The cost for doing reflection was paid each and every time the app ran. -3) .NET Native - which is used by UWP apps - has somewhat restrictions on what types/APIs can be reflected upon and complex ways of working around it. -Doing it at build time should result in developers seeing all errors up front, rather than only seeing errors for the code being tested. And the runtime cost is not incurred every single time. - -## How it works -This change adds a new targets file that is imported from the [CSharpLib.targets](https://github.com/microsoft/react-native-windows/blob/main/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppLib.targets) and [CSharpApp.targets](https://github.com/microsoft/react-native-windows/blob/main/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets). That will enable the codegen for managed projects. - -Those targets will collect all the C# source files, assembly references and defines from the app (or library) and invoke the CodeGen tool to generate a C# file. This is very similar to how the translation of Xaml to csharp code behind files works. -The generated C# file will contain a class called `ReactPackageProvider` in the default namespace of your project. The generate class implements `Microsoft.ReactNative.IReactPackageProvider`. The implementation of the generated class will: - * Register the detected ViewManagers (classes implementing `Microsoft.ReactNative.IViewManager`) - * Register the detected Modules (classes with attribute `[Microsoft.ReactNative.Managed.ReactModule]`) - * Generate and register serializers for types that need to be serialized for the registred modules. - * Generate and register custom serialization methods. - -### In source mode -The targets file hooks up an MSBuild target that will ensure the tool is compiled, deployed and runnable based on the tools sources. The codegen tool is defined in the Microsoft.ReactNative.Managed.CodeGen project. It will build and deploy the tool in the current configuraiton of your project (debug or release) but will always build and run it in platform x64. - -### In NuGet mode (Future) -The tool will be shipped as a binary and the steps won't have to build or deploy the tool. The shipped version in the NuGet package will only be release win-x64. -When building the apps that are checked in to our repo for testing, we have to remain running off the source version as MSBuild and NuGet are not flexible enough to build and consume a NuGet package in the same build. - -## Turning on/off -Currently the feature is behind an MSBuild property `$(ReactNativeCodeGenEnabled)`. -The default at the moment is false, it is only turned on for a single project for now which is the [SampleLibraryCS.csproj](https://github.com/microsoft/react-native-windows/blob/main/packages/sample-apps/windows/SampleLibraryCS/SampleLibraryCS.csproj) project, to prove it is stable. We will slowly enable it for all projects in the repo and then make it the default. - -## MSBuild/NuGet Complications -MSBuild and NuGet spent a long time fighting me in mixing a NetCoreApp3.1 executable and WinRT apps in the same solution and the same build. ProjectReferences cannot be used so I had to use the `` task directly in the targets and it was tricky making it build from both the customer apps as well as our main build solution and unittest (Microsoft.ReactNative.sln). In the end there are a few hacks in place to make this work. -The hookups in MSBuild do not use the latest BeforeTargets/AfterTargets feature to schedule the main work. I opted to follow the same pattern that the Xaml Codegen uses, so the React CodeGen runs after all the other codegen tools (like resgen, xamlcompile etc) to ensure people familiar with those extensions know how to work with the React Managed CodeGen. - -## Future improvements - * Add support for `Task` async methods. [#5143](https://github.com/microsoft/react-native-windows/issues/5143) - * Ship it as a NuGet package [#4546](https://github.com/microsoft/react-native-windows/issues/4546) diff --git a/package.json b/package.json index e901189aea2..4bf7b7d03dc 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@rnw-scripts/format-files": "*", "@rnw-scripts/integrate-rn": "*", "@rnw-scripts/just-task": "*", + "@rnw-scripts/prepare-release": "*", "@rnw-scripts/promote-release": "*", "@rnw-scripts/stamp-version": "0.0.0", "@rnw-scripts/take-screenshot": "*", @@ -50,7 +51,7 @@ "lodash": "^4.17.15" }, "resolutions": { - "react": "19.1.1", + "react": "19.2.3", "convert-source-map": "^2.0.0", "kind-of": "6.0.3", "glob-parent": "^5.1.2", @@ -64,11 +65,7 @@ "**/@react-native/tester/ws": "^6.2.3", "**/webdriverio/**/puppeteer-core/ws": "^8.17.1", "**/detox/ws": "^5.2.4", - "xml2js": "^0.5.0", - "z-schema": "^5.0.2" - }, - "resolutions.justification": { - "z-schema": "CVE-2021-3765 in validator. z-schema is used by rush which is a dependency of lage so should not be executed in this repo" + "xml2js": "^0.5.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } \ No newline at end of file diff --git a/packages/@office-iss/react-native-win32-tester/overrides.json b/packages/@office-iss/react-native-win32-tester/overrides.json index cabcb17484a..da88374e9f6 100644 --- a/packages/@office-iss/react-native-win32-tester/overrides.json +++ b/packages/@office-iss/react-native-win32-tester/overrides.json @@ -5,7 +5,7 @@ "excludePatterns": [ "src/js/examples-win32/**" ], - "baseVersion": "0.82.0-nightly-20250902-9731e8ebc", + "baseVersion": "0.84.0-nightly-20260107-58bc6c3e3", "overrides": [ { "type": "patch", @@ -42,7 +42,7 @@ "type": "derived", "file": "src/js/utils/RNTesterList.win32.js", "baseFile": "packages/rn-tester/js/utils/RNTesterList.android.js", - "baseHash": "8cd219abae140e98f9578432954fc9ef62421308" + "baseHash": "21f111f08b53ae5d60a7c03044ae64d867d93dbb" } ] } \ No newline at end of file diff --git a/packages/@office-iss/react-native-win32-tester/package.json b/packages/@office-iss/react-native-win32-tester/package.json index e55d1a91bad..c80df3bb012 100644 --- a/packages/@office-iss/react-native-win32-tester/package.json +++ b/packages/@office-iss/react-native-win32-tester/package.json @@ -11,18 +11,18 @@ "validate-overrides": "react-native-platform-override validate" }, "dependencies": { - "@react-native/tester": "0.82.0-main", + "@react-native/tester": "0.84.0-nightly-20260107-58bc6c3e3", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "flow-enums-runtime": "^0.0.6" }, "peerDependencies": { - "@office-iss/react-native-win32": "^0.0.0-canary.305", - "react": "19.1.1", - "react-native": "0.82.0-nightly-20250902-9731e8ebc" + "@office-iss/react-native-win32": "^0.0.0-canary.310", + "react": "19.2.3", + "react-native": "0.84.0-nightly-20260107-58bc6c3e3" }, "devDependencies": { - "@office-iss/react-native-win32": "^0.0.0-canary.305", + "@office-iss/react-native-win32": "^0.0.0-canary.310", "@rnw-scripts/babel-react-native-config": "0.0.0", "@rnw-scripts/eslint-config": "1.2.38", "@rnw-scripts/just-task": "2.3.58", @@ -30,8 +30,8 @@ "@types/node": "^22.14.0", "eslint": "^8.19.0", "just-scripts": "^1.3.3", - "react-native": "0.82.0-nightly-20250902-9731e8ebc", - "react-native-platform-override": "0.0.0-canary.1019", + "react-native": "0.84.0-nightly-20260107-58bc6c3e3", + "react-native-platform-override": "0.0.0-canary.1021", "typescript": "5.0.4" }, "engines": { diff --git a/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js b/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js index 032bd9fe6d5..d9c5c9608d4 100644 --- a/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js +++ b/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js @@ -78,6 +78,16 @@ const Components: Array = [ key: 'KeyboardAvoidingViewExample', module: require('../examples/KeyboardAvoidingView/KeyboardAvoidingViewExample'), }, + { + key: 'KeyEvents', + module: require('../examples/KeyboardEventsExample/KeyboardEventsExample') + .default, + }, + { + key: 'ModalExample', + category: 'UI', + module: require('../examples/Modal/ModalExample'), + }, { key: 'NewAppScreenExample', module: require('../examples/NewAppScreen/NewAppScreenExample'), @@ -330,6 +340,11 @@ const APIs: Array = ([ category: 'UI', module: require('../examples/RadialGradient/RadialGradientExample'), }, + { + key: 'BackgroundImageExample', + category: 'UI', + module: require('../examples/BackgroundImage/BackgroundImageExample'), + }, { key: 'MixBlendModeExample', category: 'UI', @@ -359,11 +374,27 @@ const APIs: Array = ([ key: 'LegacyModuleExample', module: require('../examples/TurboModule/LegacyModuleExample'), }, - { - key: 'TurboCxxModuleExample', - category: 'Basic', - module: require('../examples/TurboModule/TurboCxxModuleExample'), - }, + // Basic check to detect the availability of the IntersectionObserver API. + // $FlowExpectedError[cannot-resolve-name] + ...(typeof IntersectionObserver === 'function' + ? [ + { + key: 'IntersectionObserver', + category: 'UI', + module: require('../examples/IntersectionObserver/IntersectionObserverIndex'), + }, + ] + : []), + // Basic check to detect the availability of the modern Performance API. + ...(typeof performance.getEntries === 'function' + ? [ + { + key: 'PerformanceApiExample', + category: 'Basic', + module: require('../examples/Performance/PerformanceApiExample'), + }, + ] + : []), ...RNTesterListFbInternal.APIs, ]: Array).filter(Boolean); diff --git a/packages/@office-iss/react-native-win32/.flowconfig b/packages/@office-iss/react-native-win32/.flowconfig index 1f7153bd66d..e9a3ecc4210 100644 --- a/packages/@office-iss/react-native-win32/.flowconfig +++ b/packages/@office-iss/react-native-win32/.flowconfig @@ -144,7 +144,7 @@ module.name_mapper='^@office-iss/react-native-win32$' -> '/index.w module.name_mapper='^react-native/\(.*\)$' -> '/\1' module.name_mapper='^@office-iss/react-native-win32/\(.*\)$' -> '\/1' module.name_mapper='^@react-native/dev-middleware$' -> '/\1' -module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\)$' -> '/Libraries/Image/RelativeImageStub' +module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\|ktx\|heic\|heif\)$' -> '/Libraries/Image/RelativeImageStub' module.system.haste.module_ref_prefix=m# @@ -175,4 +175,4 @@ untyped-import untyped-type-import [version] -^0.280.0 +^0.295.0 diff --git a/packages/@office-iss/react-native-win32/CHANGELOG.json b/packages/@office-iss/react-native-win32/CHANGELOG.json index 99d2e01c46f..f8c411dc67b 100644 --- a/packages/@office-iss/react-native-win32/CHANGELOG.json +++ b/packages/@office-iss/react-native-win32/CHANGELOG.json @@ -1,6 +1,117 @@ { "name": "@office-iss/react-native-win32", "entries": [ + { + "date": "Fri, 27 Mar 2026 03:00:07 GMT", + "version": "0.0.0-canary.310", + "tag": "@office-iss/react-native-win32_v0.0.0-canary.310", + "comments": { + "prerelease": [ + { + "author": "66076509+vineethkuttan@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "e57c920b275ea89dfe7e372f7fed8b34f7ab81aa", + "comment": "Integrate 0.84.0-nightly-20260107-58bc6c3e3" + } + ] + } + }, + { + "date": "Wed, 25 Mar 2026 02:08:13 GMT", + "version": "0.0.0-canary.309", + "tag": "@office-iss/react-native-win32_v0.0.0-canary.309", + "comments": { + "prerelease": [ + { + "author": "66076509+vineethkuttan@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "70ed4bc8ac65bb551143b97576dd24ccb6bba702", + "comment": "Integrate 0.84.0-nightly-20251212-dd390dbbe" + }, + { + "author": "beachball", + "package": "@office-iss/react-native-win32", + "comment": "Bump react-native-platform-override to v0.0.0-canary.1021", + "commit": "not available" + } + ] + } + }, + { + "date": "Wed, 11 Mar 2026 03:08:37 GMT", + "version": "0.0.0-canary.308", + "tag": "@office-iss/react-native-win32_v0.0.0-canary.308", + "comments": { + "prerelease": [ + { + "author": "30809111+acoates-ms@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "8fb1889b612c06cab43d39c9c05950524655750c", + "comment": "Update require's from TouchableWin32 to be imports" + } + ] + } + }, + { + "date": "Sat, 07 Mar 2026 04:06:59 GMT", + "version": "0.0.0-canary.307", + "tag": "@office-iss/react-native-win32_v0.0.0-canary.307", + "comments": { + "prerelease": [ + { + "author": "30809111+acoates-ms@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "a0e5dd17f852aca8dd005cb75b6cc2294d411bbd", + "comment": "Fix images not showing in test app" + }, + { + "author": "66076509+vineethkuttan@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "a0e5dd17f852aca8dd005cb75b6cc2294d411bbd", + "comment": "Integrate 0.83.0-nightly-20251104-502efe1cc" + }, + { + "author": "beachball", + "package": "@office-iss/react-native-win32", + "comment": "Bump react-native-platform-override to v0.0.0-canary.1020", + "commit": "not available" + } + ] + } + }, + { + "date": "Wed, 04 Mar 2026 22:34:24 GMT", + "version": "0.0.0-canary.306", + "tag": "@office-iss/react-native-win32_v0.0.0-canary.306", + "comments": { + "prerelease": [ + { + "author": "30809111+acoates-ms@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "25db080987460d9b4b5f145aeffc5b4b0a669a72", + "comment": "Fix TextWin32 export" + }, + { + "author": "66076509+vineethkuttan@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "9a59a5e833886b5d9bdccb1bf20f99a35d7500bc", + "comment": "Integrate 0.83.0-nightly-20250917-18cb4edfa" + }, + { + "author": "30809111+acoates-ms@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "e2dc99a36053b8bbc04be9f613e33f3f7efb4cc4", + "comment": "Fix TouchableWin32 and LogBox" + }, + { + "author": "66076509+vineethkuttan@users.noreply.github.com", + "package": "@office-iss/react-native-win32", + "commit": "9cc99209e10b79a9b5223465538f81c3954bec4e", + "comment": "Integrate 0.83.0-nightly-20251012-6f482708b" + } + ] + } + }, { "date": "Tue, 13 Jan 2026 13:00:13 GMT", "version": "0.0.0-canary.305", diff --git a/packages/@office-iss/react-native-win32/CHANGELOG.md b/packages/@office-iss/react-native-win32/CHANGELOG.md index 23e16f90565..22f1f7227f4 100644 --- a/packages/@office-iss/react-native-win32/CHANGELOG.md +++ b/packages/@office-iss/react-native-win32/CHANGELOG.md @@ -1,9 +1,55 @@ # Change Log - @office-iss/react-native-win32 - + +## 0.0.0-canary.310 + +Fri, 27 Mar 2026 03:00:07 GMT + +### Changes + +- Integrate 0.84.0-nightly-20260107-58bc6c3e3 (66076509+vineethkuttan@users.noreply.github.com) + +## 0.0.0-canary.309 + +Wed, 25 Mar 2026 02:08:13 GMT + +### Changes + +- Integrate 0.84.0-nightly-20251212-dd390dbbe (66076509+vineethkuttan@users.noreply.github.com) +- Bump react-native-platform-override to v0.0.0-canary.1021 + +## 0.0.0-canary.308 + +Wed, 11 Mar 2026 03:08:37 GMT + +### Changes + +- Update require's from TouchableWin32 to be imports (30809111+acoates-ms@users.noreply.github.com) + +## 0.0.0-canary.307 + +Sat, 07 Mar 2026 04:06:59 GMT + +### Changes + +- Fix images not showing in test app (30809111+acoates-ms@users.noreply.github.com) +- Integrate 0.83.0-nightly-20251104-502efe1cc (66076509+vineethkuttan@users.noreply.github.com) +- Bump react-native-platform-override to v0.0.0-canary.1020 + +## 0.0.0-canary.306 + +Wed, 04 Mar 2026 22:34:24 GMT + +### Changes + +- Fix TextWin32 export (30809111+acoates-ms@users.noreply.github.com) +- Integrate 0.83.0-nightly-20250917-18cb4edfa (66076509+vineethkuttan@users.noreply.github.com) +- Fix TouchableWin32 and LogBox (30809111+acoates-ms@users.noreply.github.com) +- Integrate 0.83.0-nightly-20251012-6f482708b (66076509+vineethkuttan@users.noreply.github.com) + ## 0.0.0-canary.305 Tue, 13 Jan 2026 13:00:13 GMT diff --git a/packages/@office-iss/react-native-win32/overrides.json b/packages/@office-iss/react-native-win32/overrides.json index 08a01083799..5f733c4f0b3 100644 --- a/packages/@office-iss/react-native-win32/overrides.json +++ b/packages/@office-iss/react-native-win32/overrides.json @@ -7,19 +7,25 @@ "**/__snapshots__/**", "src-win/rntypes/**" ], - "baseVersion": "0.82.0-nightly-20250902-9731e8ebc", + "baseVersion": "0.84.0-nightly-20260107-58bc6c3e3", "overrides": [ { "type": "derived", "file": ".flowconfig", "baseFile": ".flowconfig", - "baseHash": "c8bd25d4aa75328a2484c5c7b0b768c188a50b52" + "baseHash": "49be0404a6bbdf103616a7506ad8a27874048119" }, { "type": "derived", "file": "src-win/index.win32.js", "baseFile": "packages/react-native/index.js", - "baseHash": "88aa62f2d86a810631bcbc871f45c28c53fceec7" + "baseHash": "43b61164095fb1f33793316908d5f974e7c46651" + }, + { + "type": "derived", + "file": "src-win/index.win32.js.flow", + "baseFile": "packages/react-native/index.js.flow", + "baseHash": "da9cb1941e2b3d395d157e64a56b71a26396f2bf" }, { "type": "platform", @@ -35,13 +41,13 @@ "type": "derived", "file": "src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts", "baseFile": "packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts", - "baseHash": "30a3c7351c6a466e2ed95e9b416907ac677d71ca" + "baseHash": "34bb1374c3d892607c894ad676de1e5d104ffaae" }, { "type": "derived", "file": "src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js", "baseFile": "packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js", - "baseHash": "b77b87e32a86265a3c8c29ebbbee46558596aec8" + "baseHash": "c0c7037e52d48caae88cffaac7f60d4dd1c32b3e" }, { "type": "copy", @@ -60,7 +66,7 @@ "type": "derived", "file": "src-win/Libraries/Components/Button.win32.js", "baseFile": "packages/react-native/Libraries/Components/Button.js", - "baseHash": "26d3afc77f82939021f848eb8185b3b441fb3c7d" + "baseHash": "ef86e2343f5213a5f230c63a1ddf12cbe6fa946c" }, { "type": "platform", @@ -85,7 +91,7 @@ "type": "patch", "file": "src-win/Libraries/Components/Pressable/Pressable.win32.js", "baseFile": "packages/react-native/Libraries/Components/Pressable/Pressable.js", - "baseHash": "08a6e1c0dd895af70ea1c8448a3be028d9cd2587", + "baseHash": "b8d87e48400468e702509ad24dab05f0a492aa19", "issue": 6240 }, { @@ -108,13 +114,13 @@ "type": "derived", "file": "src-win/Libraries/Components/TextInput/TextInput.flow.win32.js", "baseFile": "packages/react-native/Libraries/Components/TextInput/TextInput.flow.js", - "baseHash": "c3e71a2dbba8e4dd45a34c0c21327aa36d79a1b1" + "baseHash": "e36bacf69caef9e59e1f62f9ee4335c6879ef0ed" }, { "type": "derived", "file": "src-win/Libraries/Components/TextInput/TextInput.win32.js", "baseFile": "packages/react-native/Libraries/Components/TextInput/TextInput.js", - "baseHash": "547fa57cd844cece1f4401b93be75b2edd2a757f" + "baseHash": "cc397bb3c850bb0a02f4b3134fdcbc2d9b597b80" }, { "type": "patch", @@ -153,7 +159,7 @@ "type": "derived", "file": "src-win/Libraries/Components/Touchable/TouchableNativeFeedback.win32.js", "baseFile": "packages/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.js", - "baseHash": "2ded2efb727d7544aa8a0b249dd3f2502bb57129" + "baseHash": "d48f21572fc72cf6d79df5aac775fb67d4f1abb4" }, { "type": "platform", @@ -201,14 +207,14 @@ "type": "patch", "file": "src-win/Libraries/Components/View/ViewPropTypes.win32.js", "baseFile": "packages/react-native/Libraries/Components/View/ViewPropTypes.js", - "baseHash": "d04b64a97b6040611d96b909c41230c1a350ebe6", + "baseHash": "3f85fd36b8c098a91f965e9150e53d77ff3aa432", "issue": 6240 }, { "type": "derived", "file": "src-win/Libraries/Components/View/ViewWin32.d.ts", "baseFile": "packages/react-native/Libraries/Components/View/View.d.ts", - "baseHash": "c71957187cbae0b40995198a2d7a18d5cae6d662" + "baseHash": "27bf94d4a6d5ae4034c2da165fb5bc06fd98d1a3" }, { "type": "platform", @@ -218,7 +224,7 @@ "type": "patch", "file": "src-win/Libraries/Core/Devtools/loadBundleFromServer.win32.js", "baseFile": "packages/react-native/Libraries/Core/Devtools/loadBundleFromServer.js", - "baseHash": "5177ecc736e9bebd4e798dcc2c6dd19c38b0a937", + "baseHash": "920db91a9af73d35420692d31beb1e4a8e068ff1", "issue": 12704 }, { @@ -236,7 +242,7 @@ "type": "derived", "file": "src-win/Libraries/Image/Image.win32.js", "baseFile": "packages/react-native/Libraries/Image/Image.ios.js", - "baseHash": "be81d302b8f2d64ddf7a52bd30fbcb47d18741b5", + "baseHash": "e52a6144ed3241e1e31e87292536908f8088d9c0", "issue": 4320 }, { @@ -335,7 +341,7 @@ "type": "copy", "file": "src-win/Libraries/Network/RCTNetworking.win32.js", "baseFile": "packages/react-native/Libraries/Network/RCTNetworking.ios.js", - "baseHash": "1c7dcc3e3cf7339c3450e7722b7b0a355266a001", + "baseHash": "6cc93d00e1b6f541d08102cdc5363184f7ed6025", "issue": 4318 }, { @@ -365,7 +371,7 @@ "type": "patch", "file": "src-win/Libraries/Pressability/Pressability.win32.js", "baseFile": "packages/react-native/Libraries/Pressability/Pressability.js", - "baseHash": "a4205b3bea276e65da3ce0aeb6753bc8e7c5ead0", + "baseHash": "10801e10120946aa4c721d13f3b8f455d0f43db8", "issue": 6240 }, { @@ -388,13 +394,13 @@ "type": "patch", "file": "src-win/Libraries/ReactNative/PaperUIManager.win32.js", "baseFile": "packages/react-native/Libraries/ReactNative/PaperUIManager.js", - "baseHash": "7a14c3d42ba7442848bd90329f6d31bda323c6d4" + "baseHash": "2c0076cc934fffa7c0a91721ee4268584289c6ec" }, { "type": "patch", "file": "src-win/Libraries/Renderer/shims/ReactNativeTypes.win32.js", "baseFile": "packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js", - "baseHash": "9e91759d01c9a6d459c5781b357533f4ac8727a7", + "baseHash": "41da351b1e8d090e5ecf0859738218a5dcd4c08f", "issue": 0 }, { @@ -419,32 +425,32 @@ "type": "derived", "file": "src-win/Libraries/Text/Text.d.ts", "baseFile": "packages/react-native/Libraries/Text/Text.d.ts", - "baseHash": "3540a1a36fc487e54c1cec9b0c57b4fdc2124b60" + "baseHash": "0289a5c0fbb168f20602451fe314f3787f1ac66f" }, { "type": "derived", "file": "src-win/Libraries/Text/Text.win32.js", "baseFile": "packages/react-native/Libraries/Text/Text.js", - "baseHash": "59bf5757acc083a749a38173b3610529b27ef4fb" + "baseHash": "914f50544d09bc3bfe2ea3e34951e47e52d2dd87" }, { "type": "derived", "file": "src-win/Libraries/Text/TextNativeComponent.win32.js", "baseFile": "packages/react-native/Libraries/Text/TextNativeComponent.js", - "baseHash": "259a625b5f7cf3a70ecd4af6388d82d47f446e50", + "baseHash": "e1c95809fd0819c130f77dc2a61399b01b5dd687", "issue": 7074 }, { "type": "derived", "file": "src-win/Libraries/Text/TextProps.win32.js", "baseFile": "packages/react-native/Libraries/Text/TextProps.js", - "baseHash": "7ee3be32c351343aa65a3fd12e568265bb788d9f" + "baseHash": "ddeedf097aec999824c4e9d246fdf6fefd61dc76" }, { "type": "patch", "file": "src-win/Libraries/Types/CoreEventTypes.win32.js", "baseFile": "packages/react-native/Libraries/Types/CoreEventTypes.js", - "baseHash": "ed736df6081d2fbbf54a646b8373bf485449255a", + "baseHash": "5834bb306dcf85772546feae7af58a875196d309", "issue": 6240 }, { @@ -497,7 +503,7 @@ "type": "patch", "file": "src-win/src/private/animated/NativeAnimatedHelper.win32.js", "baseFile": "packages/react-native/src/private/animated/NativeAnimatedHelper.js", - "baseHash": "14407780e54e66515159aed1a51cee154b1d5339", + "baseHash": "90277c8c54231153433c8e5ef9d0478a41a9138c", "issue": 11041 }, { diff --git a/packages/@office-iss/react-native-win32/package.json b/packages/@office-iss/react-native-win32/package.json index e46eee49b9f..01a1c4d209e 100644 --- a/packages/@office-iss/react-native-win32/package.json +++ b/packages/@office-iss/react-native-win32/package.json @@ -1,6 +1,6 @@ { "name": "@office-iss/react-native-win32", - "version": "0.0.0-canary.305", + "version": "0.0.0-canary.310", "description": "Implementation of react native on top of Office's Win32 platform.", "repository": { "type": "git", @@ -12,14 +12,14 @@ "types": "types", "scripts": { "build": "rnw-scripts build", - "bundle": "rnw-scripts prepareBundle && npx @react-native-community/cli bundle --platform win32 --entry-file ../react-native-win32-tester/js/RNTesterApp.win32.js --bundle-output dist/win32/dev/js/RNTesterApp.bundle --assets-dest dist/win32/dev --sourcemap-output ./dist/win32/dev/js/RNTesterApp.bundle.map", + "bundle": "rnw-scripts prepareBundle && npx @react-native-community/cli rnx-bundle --platform win32 --entry-file ../react-native-win32-tester/js/RNTesterApp.win32.js --bundle-output dist/win32/dev/js/RNTesterApp.bundle --assets-dest dist/win32/dev --sourcemap-output ./dist/win32/dev/js/RNTesterApp.bundle.map", "clean": "rnw-scripts clean", "flow-check": "rnw-scripts flow-check", "lint:fix": "rnw-scripts lint:fix", "lint": "rnw-scripts lint", "run-win32-devmain": "npx @office-iss/rex-win32@0.71.15-devmain.16607.10000 --bundle js/RNTesterApp --component RNTesterApp --basePath ./dist/win32/dev --jsEngine v8 --useDevMain --useDirectDebugger --useFastRefresh", "run-win32": "npx @office-iss/rex-win32@0.71.15-devmain.16607.10000 --bundle js/RNTesterApp --component RNTesterApp --basePath ./dist/win32/dev --jsEngine v8 --useDirectDebugger --useFastRefresh", - "start": "npx @react-native-community/cli start --projectRoot ../react-native-win32-tester", + "start": "npx @react-native-community/cli rnx-start --projectRoot ../react-native-win32-tester", "test": "jest", "validate-overrides": "react-native-platform-override validate" }, @@ -30,13 +30,13 @@ "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", "@react-native/assets": "1.0.0", - "@react-native/assets-registry": "0.82.0-nightly-20250902-9731e8ebc", - "@react-native/codegen": "0.82.0-nightly-20250902-9731e8ebc", - "@react-native/community-cli-plugin": "0.82.0-nightly-20250902-9731e8ebc", - "@react-native/gradle-plugin": "0.82.0-nightly-20250902-9731e8ebc", - "@react-native/js-polyfills": "0.82.0-nightly-20250902-9731e8ebc", - "@react-native/normalize-colors": "0.82.0-nightly-20250902-9731e8ebc", - "@react-native/virtualized-lists": "0.82.0-nightly-20250902-9731e8ebc", + "@react-native/assets-registry": "0.84.0-nightly-20260107-58bc6c3e3", + "@react-native/codegen": "0.84.0-nightly-20260107-58bc6c3e3", + "@react-native/community-cli-plugin": "0.84.0-nightly-20260107-58bc6c3e3", + "@react-native/gradle-plugin": "0.84.0-nightly-20260107-58bc6c3e3", + "@react-native/js-polyfills": "0.84.0-nightly-20260107-58bc6c3e3", + "@react-native/normalize-colors": "0.84.0-nightly-20260107-58bc6c3e3", + "@react-native/virtualized-lists": "0.84.0-nightly-20260107-58bc6c3e3", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -49,11 +49,12 @@ "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", + "hermes-compiler": "0.14.0-commitly-202512102158-39fca9fda", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.83.1", - "metro-source-map": "^0.83.1", + "metro-runtime": "^0.83.3", + "metro-source-map": "^0.83.3", "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", @@ -62,40 +63,42 @@ "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.26.0", + "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.3", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "devDependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", - "@react-native/metro-config": "0.82.0-nightly-20250902-9731e8ebc", + "@react-native/metro-config": "0.84.0-nightly-20260107-58bc6c3e3", "@rnw-scripts/babel-react-native-config": "0.0.0", "@rnw-scripts/eslint-config": "1.2.38", "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "^1.1.42", "@rnw-scripts/just-task": "2.3.58", "@rnw-scripts/metro-dev-config": "0.0.0", - "@rnx-kit/jest-preset": "^0.1.17", + "@rnx-kit/cli": "^1.1.0", + "@rnx-kit/jest-preset": "^0.3.1", "@types/node": "^22.0.0", "@types/prop-types": "15.7.1", - "@types/react": "^19.0.0", + "@types/react": "^19.2.3", "eslint": "^8.19.0", - "flow-bin": "^0.280.0", + "flow-bin": "^0.295.0", "jscodeshift": "^0.14.0", "just-scripts": "^1.3.3", - "prettier": "2.8.8", - "react": "19.1.1", - "react-native": "0.82.0-nightly-20250902-9731e8ebc", - "react-native-platform-override": "0.0.0-canary.1019", + "prettier": "^3.6.2", + "react": "19.2.3", + "react-native": "0.84.0-nightly-20260107-58bc6c3e3", + "react-native-platform-override": "0.0.0-canary.1021", "typescript": "5.0.4" }, "peerDependencies": { - "@types/react": "^19.1.1", - "react": "^19.1.1", - "react-native": "0.82.0-nightly-20250902-9731e8ebc" + "@types/react": "^19.2.3", + "react": "^19.2.3", + "react-native": "0.84.0-nightly-20260107-58bc6c3e3" }, "beachball": { "defaultNpmTag": "canary", @@ -109,6 +112,18 @@ ] }, "promoteRelease": true, + "rnx-kit": { + "bundle": { + "plugins": [ + [ + "@rnx-kit/metro-plugin-cyclic-dependencies-detector", + { + "throwOnError": false + } + ] + ] + } + }, "engines": { "node": ">= 22" } diff --git a/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts b/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts index cdb508f27a5..d33050d8a2d 100644 --- a/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts +++ b/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts @@ -150,12 +150,18 @@ export interface AccessibilityInfoStatic { * - `announcement`: The string announced by the screen reader. * - `options`: An object that configures the reading options. * - `queue`: The announcement will be queued behind existing announcements. iOS only. + * - `priority`: The priority of the announcement. Possible values: 'low' | 'default' | 'high'. + * High priority announcements will interrupt any ongoing speech and cannot be interrupted. + * Default priority announcements will interrupt any ongoing speech but can be interrupted. + * Low priority announcements will not interrupt ongoing speech and can be interrupted. + * (iOS only). * - `nativeID`: The nativeID of the element to send the announcement from. win32 only. */ announceForAccessibilityWithOptions( announcement: string, options: { queue?: boolean | undefined; + priority?: 'low' | 'default' | 'high' | undefined; nativeID?: string | undefined; // win32 }, ): void; diff --git a/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js b/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js index efd8dc05ee3..d6c48a728f9 100644 --- a/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js +++ b/packages/@office-iss/react-native-win32/src-win/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js @@ -44,7 +44,11 @@ type AccessibilityEventDefinitions = { screenReaderChanged: [boolean], }; -type AccessibilityEventTypes = 'click' | 'focus' | 'viewHoverEnter'; +type AccessibilityEventTypes = + | 'click' + | 'focus' + | 'viewHoverEnter' + | 'windowStateChange'; // Mapping of public event names to platform-specific event names. const EventNames: Map< @@ -107,7 +111,7 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject(new Error('NativeAccessibilityManagerIOS is not available')); } }); } @@ -127,7 +131,11 @@ const AccessibilityInfo = { if (NativeAccessibilityInfoAndroid?.isGrayscaleEnabled != null) { NativeAccessibilityInfoAndroid.isGrayscaleEnabled(resolve); } else { - reject(null); + reject( + new Error( + 'NativeAccessibilityInfoAndroid.isGrayscaleEnabled is not available', + ), + ); } }); } else if (Platform.OS === 'win32') { @@ -140,7 +148,7 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject(new Error('AccessibilityInfo native module is not available')); } }); } @@ -160,7 +168,11 @@ const AccessibilityInfo = { if (NativeAccessibilityInfoAndroid?.isInvertColorsEnabled != null) { NativeAccessibilityInfoAndroid.isInvertColorsEnabled(resolve); } else { - reject(null); + reject( + new Error( + 'NativeAccessibilityInfoAndroid.isInvertColorsEnabled is not available', + ), + ); } }); } else if (Platform.OS === 'win32') { @@ -173,7 +185,7 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject(new Error('AccessibilityInfo native module is not available')); } }); } @@ -199,7 +211,7 @@ const AccessibilityInfo = { if (NativeAccessibilityInfoWin32 != null) { NativeAccessibilityInfoWin32.isReduceMotionEnabled(resolve); } else { - reject(null); + reject(new Error('AccessibilityInfo native module is not available')); } } else { if (NativeAccessibilityManagerIOS != null) { @@ -208,7 +220,7 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject(new Error('NativeAccessibilityManagerIOS is not available')); } } }); @@ -226,7 +238,11 @@ const AccessibilityInfo = { if (NativeAccessibilityInfo?.isHighTextContrastEnabled != null) { NativeAccessibilityInfo.isHighTextContrastEnabled(resolve); } else { - reject(null); + reject( + new Error( + 'NativeAccessibilityInfoAndroid.isHighTextContrastEnabled is not available', + ), + ); } } else { return Promise.resolve(false); @@ -254,7 +270,11 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject( + new Error( + 'NativeAccessibilityManagerIOS.getCurrentDarkerSystemColorsState is not available', + ), + ); } } }); @@ -282,7 +302,11 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject( + new Error( + 'NativeAccessibilityManagerIOS.getCurrentPrefersCrossFadeTransitionsState is not available', + ), + ); } } }); @@ -307,7 +331,7 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject(new Error('NativeAccessibilityManagerIOS is not available')); } }); } @@ -333,7 +357,7 @@ const AccessibilityInfo = { if (NativeAccessibilityInfoWin32 != null) { NativeAccessibilityInfoWin32.isTouchExplorationEnabled(resolve); } else { - reject(null); + reject(new Error('NativeAccessibilityInfoAndroid is not available')); } } else { if (NativeAccessibilityManagerIOS != null) { @@ -342,7 +366,7 @@ const AccessibilityInfo = { reject, ); } else { - reject(null); + reject(new Error('NativeAccessibilityManagerIOS is not available')); } } }); @@ -367,10 +391,18 @@ const AccessibilityInfo = { ) { NativeAccessibilityInfo.isAccessibilityServiceEnabled(resolve); } else { - reject(null); + reject( + new Error( + 'NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled is not available', + ), + ); } } else { - reject(null); + reject( + new Error( + 'isAccessibilityServiceEnabled is only available on Android', + ), + ); } }); }, @@ -434,6 +466,8 @@ const AccessibilityInfo = { * Set accessibility focus to a React component. * * See https://reactnative.dev/docs/accessibilityinfo#setaccessibilityfocus + * + * @deprecated Use `sendAccessibilityEvent` with eventType `focus` instead. */ setAccessibilityFocus(reactTag: number): void { legacySendAccessibilityEvent(reactTag, 'focus'); @@ -474,12 +508,18 @@ const AccessibilityInfo = { * - `announcement`: The string announced by the screen reader. * - `options`: An object that configures the reading options. * - `queue`: The announcement will be queued behind existing announcements. iOS only. + * - `priority`: The priority of the announcement. Possible values: 'low' | 'default' | 'high'. + * High priority announcements will interrupt any ongoing speech and cannot be interrupted. + * Default priority announcements will interrupt any ongoing speech but can be interrupted. + * Low priority announcements will not interrupt ongoing speech and can be interrupted. + * (iOS only). * - `nativeID`: The nativeID of the element to send the announcement from. win32 only. */ announceForAccessibilityWithOptions( announcement: string, options: { queue?: boolean, + priority?: 'low' | 'default' | 'high', nativeID?: string, // win32 }, ): void { @@ -487,9 +527,10 @@ const AccessibilityInfo = { NativeAccessibilityInfo?.announceForAccessibility(announcement); } else if (Platform.OS === 'win32') { if (NativeAccessibilityInfoWin32?.announceForAccessibilityWithOptions) { + const {priority: _, ...win32Options} = options; NativeAccessibilityInfoWin32?.announceForAccessibilityWithOptions( announcement, - options, + win32Options, ); } else { NativeAccessibilityInfoWin32?.announceForAccessibility(announcement); diff --git a/packages/@office-iss/react-native-win32/src-win/Libraries/Components/Button.win32.js b/packages/@office-iss/react-native-win32/src-win/Libraries/Components/Button.win32.js index 12afdda284b..e80c74f4c4e 100644 --- a/packages/@office-iss/react-native-win32/src-win/Libraries/Components/Button.win32.js +++ b/packages/@office-iss/react-native-win32/src-win/Libraries/Components/Button.win32.js @@ -39,7 +39,7 @@ export type ButtonProps = $ReadOnly<{ Handler to be called when the user taps the button. The first function argument is an event in form of [GestureResponderEvent](pressevent). */ - onPress?: (event?: GestureResponderEvent) => mixed, + onPress?: (event?: GestureResponderEvent) => unknown, /** If `true`, doesn't play system sound on touch. @@ -149,7 +149,7 @@ export type ButtonProps = $ReadOnly<{ */ accessible?: ?boolean, accessibilityActions?: ?$ReadOnlyArray, - onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed, + onAccessibilityAction?: ?(event: AccessibilityActionEvent) => unknown, accessibilityState?: ?AccessibilityState, /** @@ -187,9 +187,6 @@ export type ButtonProps = $ReadOnly<{ [button:source]: https://github.com/facebook/react-native/blob/HEAD/Libraries/Components/Button.js - [button:examples]: - https://js.coach/?menu%5Bcollections%5D=React%20Native&page=1&query=button - ```jsx