From 7ac9e53e8f2f783a41591f3936c5e11d7fb7d6f3 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 17 Mar 2026 15:18:28 -0700 Subject: [PATCH 1/4] Apply publish and release pipelines improvements --- .ado/jobs/desktop.yml | 12 +- .ado/jobs/setup.yml | 16 +- .ado/jobs/universal.yml | 11 +- .ado/prepare-release-bot.yml | 79 +++ .ado/publish.yml | 304 +++------- .ado/release.yml | 220 +++++-- .ado/scripts/build.js | 195 +++++++ .ado/scripts/npmGroupByTag.js | 202 ------- .ado/scripts/npmPack.js | 500 ++++++++++++++++ .ado/scripts/setVersionEnvVars.js | 14 +- ...ticate-office-react-native-windows-bot.yml | 11 - .ado/templates/checkout-shallow.yml | 2 +- .ado/templates/esrp-codesign-binaries.yml | 45 ++ .ado/templates/esrp-codesign-nuget.yml | 39 ++ .ado/templates/prep-and-pack-nuget.yml | 6 - .ado/templates/prep-and-pack-single.yml | 99 +--- .ado/templates/publish-nuget-to-ado-feed.yml | 106 ++++ .ado/templates/verdaccio-start.yml | 15 +- .gitignore | 3 + package.json | 1 + .../@rnw-scripts/prepare-release/.eslintrc.js | 4 + .../@rnw-scripts/prepare-release/.gitignore | 2 + packages/@rnw-scripts/prepare-release/bin.js | 11 + .../@rnw-scripts/prepare-release/package.json | 46 ++ .../prepare-release/src/beachballBump.ts | 49 ++ .../@rnw-scripts/prepare-release/src/git.ts | 128 +++++ .../prepare-release/src/github.ts | 139 +++++ .../prepare-release/src/prepareRelease.ts | 397 +++++++++++++ .../@rnw-scripts/prepare-release/src/proc.ts | 120 ++++ .../prepare-release/src/releaseSummary.ts | 139 +++++ .../prepare-release/tsconfig.json | 5 + .../PropertySheets/CIBuildOptimizations.props | 29 + vnext/ReactWindows-Desktop.Publish.slnf | 14 + yarn.lock | 544 +++++++++++++++++- 34 files changed, 2892 insertions(+), 615 deletions(-) create mode 100644 .ado/prepare-release-bot.yml create mode 100644 .ado/scripts/build.js delete mode 100644 .ado/scripts/npmGroupByTag.js create mode 100644 .ado/scripts/npmPack.js delete mode 100644 .ado/templates/authenticate-office-react-native-windows-bot.yml create mode 100644 .ado/templates/esrp-codesign-binaries.yml create mode 100644 .ado/templates/esrp-codesign-nuget.yml create mode 100644 .ado/templates/publish-nuget-to-ado-feed.yml create mode 100644 packages/@rnw-scripts/prepare-release/.eslintrc.js create mode 100644 packages/@rnw-scripts/prepare-release/.gitignore create mode 100644 packages/@rnw-scripts/prepare-release/bin.js create mode 100644 packages/@rnw-scripts/prepare-release/package.json create mode 100644 packages/@rnw-scripts/prepare-release/src/beachballBump.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/git.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/github.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/prepareRelease.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/proc.ts create mode 100644 packages/@rnw-scripts/prepare-release/src/releaseSummary.ts create mode 100644 packages/@rnw-scripts/prepare-release/tsconfig.json create mode 100644 vnext/PropertySheets/CIBuildOptimizations.props create mode 100644 vnext/ReactWindows-Desktop.Publish.slnf diff --git a/.ado/jobs/desktop.yml b/.ado/jobs/desktop.yml index 92429414996..e1cfc6e872c 100644 --- a/.ado/jobs/desktop.yml +++ b/.ado/jobs/desktop.yml @@ -341,17 +341,9 @@ jobs: - template: ../templates/stop-packagers.yml - - task: BinSkim@4 - displayName: Run Binskim Analysis + - script: node .ado/scripts/build.js --binskim --platform ${{ matrix.BuildPlatform }} --configuration ${{ matrix.BuildConfiguration }} --target desktop + 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: diff --git a/.ado/jobs/setup.yml b/.ado/jobs/setup.yml index adcfab566c3..d328ea65d9a 100644 --- a/.ado/jobs/setup.yml +++ b/.ado/jobs/setup.yml @@ -34,18 +34,26 @@ jobs: - script: npx lage build --scope @rnw-scripts/beachball-config --no-deps displayName: Build @rnw-scripts/beachball-config - + + - script: | + echo "System.PullRequest.SourceBranch = $(System.PullRequest.SourceBranch)" + echo "Build.SourceBranch = $(Build.SourceBranch)" + echo "Build.SourceBranchName = $(Build.SourceBranchName)" + displayName: Print branch variables + - pwsh: | - npx beachball check --branch origin/$(BeachBallBranchName) --verbose 2>&1 | Tee-Object -Variable beachballOutput + npx --yes beachball check --branch origin/$(BeachBallBranchName) --verbose 2>&1 | Tee-Object -Variable beachballOutput $beachballErrors = $beachballOutput | Where-Object { $_ -match "ERROR: *"} $beachballErrors | ForEach { Write-Host "##vso[task.logissue type=warning]POSSIBLE $_" } displayName: Warn for possible invalid change files + condition: not(startsWith(variables['System.PullRequest.SourceBranch'], 'prepare-release/')) - ${{ if endsWith(parameters.buildEnvironment, 'PullRequest') }}: - - script: npx beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run \"yarn change\" from root of repo to generate a change file." + - script: npx --yes beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run \"yarn change\" from root of repo to generate a change file." displayName: Check for change files + condition: not(startsWith(variables['System.PullRequest.SourceBranch'], 'prepare-release/')) - - script: npx beachball bump --branch origin/$(BeachBallBranchName) --yes --verbose + - script: npx --yes beachball bump --branch origin/$(BeachBallBranchName) --yes --verbose displayName: beachball bump - template: ../templates/set-version-vars.yml diff --git a/.ado/jobs/universal.yml b/.ado/jobs/universal.yml index 79999bc65ff..85971dfdfa5 100644 --- a/.ado/jobs/universal.yml +++ b/.ado/jobs/universal.yml @@ -244,16 +244,9 @@ ${{ else }}: artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} - - task: BinSkim@4 - displayName: Run Binskim Analysis + - script: node .ado/scripts/build.js --binskim --platform ${{ matrix.BuildPlatform }} --configuration ${{ matrix.BuildConfiguration }} --target universal + 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 diff --git a/.ado/prepare-release-bot.yml b/.ado/prepare-release-bot.yml new file mode 100644 index 00000000000..89bd28de7cb --- /dev/null +++ b/.ado/prepare-release-bot.yml @@ -0,0 +1,79 @@ +name: $(Date:yyyyMMdd).$(Rev:r) + +# Triggers are configured in the ADO pipeline UI: +# - CI triggers on pushes to main and *-stable branches +# - Scheduled triggers for daily runs +# - Manual runs with optional branch override +trigger: none +pr: none + +parameters: + - name: targetBranch + displayName: Target branch for version bump (use default to use pipeline source branch) + type: string + default: (source branch) + values: + - (source branch) + - main + - 0.82-stable + - 0.81-stable + - 0.80-stable + - 0.74-stable + +jobs: + - job: PrepareRelease + displayName: Prepare Release Bot + pool: + vmImage: windows-latest + timeoutInMinutes: 30 + + steps: + - checkout: self + persistCredentials: true + fetchDepth: 1 + fetchTags: false + + - script: | + git config user.name "React-Native-Windows Bot" + git config user.email "53619745+rnbot@users.noreply.github.com" + displayName: Configure Git Identity + + # Extract OAuth token from persistCredentials for GitHub API access (gh CLI) + - pwsh: | + $headerLine = git config --get-regexp "http.*\.extraheader" 2>$null | Select-Object -First 1 + if (-not $headerLine) { + Write-Host "##[error]No HTTP extraheader found. persistCredentials may not be working." + exit 1 + } + $encoded = ($headerLine.Split(' ')[-1]).Trim() + $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded)) + $token = $decoded.Split(':')[-1] + Write-Host "Extracted GitHub OAuth token (length=$($token.Length))" + Write-Host "##vso[task.setvariable variable=GitHubOAuthToken;issecret=true]$token" + displayName: Extract GitHub OAuth token + + - task: NodeTool@0 + displayName: Set Node Version + inputs: + versionSpec: '24.x' + + - script: if not exist %APPDATA%\npm (mkdir %APPDATA%\npm) + displayName: Ensure npm directory for npx commands + + - script: npx --yes midgard-yarn@1.23.34 --ignore-scripts --frozen-lockfile + displayName: yarn install + + - script: npx lage build --scope @rnw-scripts/prepare-release --scope @rnw-scripts/beachball-config + displayName: Build prepare-release and dependencies + + - ${{ if ne(parameters.targetBranch, '(source branch)') }}: + - pwsh: Write-Host "##vso[task.setvariable variable=TargetBranch]${{ parameters.targetBranch }}" + displayName: Set target branch from parameter + - ${{ else }}: + - pwsh: Write-Host "##vso[task.setvariable variable=TargetBranch]$(Build.SourceBranchName)" + displayName: Set target branch from source + + - script: npx prepare-release --branch $(TargetBranch) --no-color + displayName: Prepare Release + env: + GH_TOKEN: $(GitHubOAuthToken) diff --git a/.ado/publish.yml b/.ado/publish.yml index 22f0333842a..07ccbea45bf 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -1,22 +1,6 @@ name: 0.0.$(Date:yyMM.d)$(Rev:rrr) parameters: -- name: skipNpmPublish - displayName: Skip Npm Publish - type: boolean - default: false -- name: skipGitPush - displayName: Skip Git Push - type: boolean - default: false -- name: stopOnNoCI - displayName: Stop if latest commit is ***NO_CI*** - type: boolean - default: true -- name: performBeachballCheck - displayName: Perform Beachball Check (Disable when promoting) - type: boolean - default: true - name: AgentPool type: object default: @@ -118,30 +102,10 @@ parameters: BuildConfiguration: Release BuildPlatform: ARM64 UseFabric: true - - Name: X64DebugFabric - BuildConfiguration: Debug - BuildPlatform: x64 - UseFabric: true - - Name: X86DebugFabric - BuildConfiguration: Debug - BuildPlatform: x86 - UseFabric: true - - Name: Arm64DebugFabric - BuildConfiguration: Debug - BuildPlatform: ARM64 - UseFabric: true variables: - template: variables/windows.yml - group: RNW Secrets - - name: SkipGitPushPublishArgs - value: '' - - name: FailCGOnAlert - value: false - - name: EnableCodesign - value: false - - name: SourceBranchWithFolders - value: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] trigger: none pr: none @@ -156,155 +120,60 @@ extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: ${{ parameters.AgentPool.Medium }} - customBuildTags: - - ES365AIMigrationTooling + 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: RNWPublish jobs: - - job: RnwPublishPrep - displayName: React-Native-Windows Publish Prep + # Set version variables + - job: SetVersionVars + displayName: Set Version Variables pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 120 - cancelTimeoutInMinutes: 5 + timeoutInMinutes: 15 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 + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/set-version-vars.yml@self parameters: - persistCredentials: false # We're going to use rnbot's git creds to publish + buildEnvironment: Continuous - - powershell: gci env:/BUILD_* - displayName: Show build information + # We new npmPack.js in Release pipeline to detect already published NPM packages and avoid publishing them again + - script: copy ".ado\scripts\npmPack.js" "$(Build.StagingDirectory)\versionEnvVars\npmPack.js" + displayName: Include npmPack.js in VersionEnvVars artifact - - template: .ado/templates/prepare-js-env.yml@self + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Publish version variables' + targetPath: $(Build.StagingDirectory)/versionEnvVars + artifactName: VersionEnvVars - - 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 + # 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 - - template: .ado/templates/configure-git.yml@self - - - pwsh: | - Write-Host "##vso[task.setvariable variable=SkipGitPushPublishArgs]--no-push" - displayName: Enable No-Publish (git) - condition: ${{ parameters.skipGitPush }} - - # Beachball publishes NPM packages to the "$(Pipeline.Workspace)\published-packages" folder. - # It pushes NPM version updates to Git depending on the SkipGitPushPublishArgs variable derived from the skipGitPush parameter. - - script: | - if exist "$(Pipeline.Workspace)\published-packages" rd /s /q "$(Pipeline.Workspace)\published-packages" - mkdir "$(Pipeline.Workspace)\published-packages" - npx beachball publish --no-publish $(SkipGitPushPublishArgs) --pack-to-path "$(Pipeline.Workspace)\published-packages" --branch origin/$(SourceBranchWithFolders) -yes --bump-deps --verbose --access public --message "applying package updates ***NO_CI***" - displayName: Beachball Publish + - script: node .ado/scripts/npmPack.js --clean --no-color "$(Pipeline.Workspace)\published-packages" + displayName: Pack npm packages - script: dir /s "$(Pipeline.Workspace)\published-packages" displayName: Show created npm packages - # Beachball usually takes care about the NPM package tagging based on the values in package.json files. - # We use the ESRP Release where we must provide the tag explictly (the productstate parameter). - # Fortunately, we just use two tags: latest and some custom tag like "canary", "v0.73-stable", etc. - # The npmGroupByTag.js script groups the created NPM package by these two tags into the specified folders. - - pwsh: | - node .ado/scripts/npmGroupByTag.js "$(Pipeline.Workspace)\published-packages" "$(Pipeline.Workspace)\published-packages\custom-tag" "$(Pipeline.Workspace)\published-packages\latest-tag" - displayName: Group npm packages by tag - - - script: dir /s "$(Pipeline.Workspace)\published-packages" - displayName: Show grouped npm packages by tag - - # Publish NPM packages using ESRP Release task with the custom tag such as "canary", "v0.73-stable", etc. - - task: 'SFP.release-tasks.custom-build-release-task.EsrpRelease@10' - displayName: 'ESRP Release to npmjs.com (custom tag)' - condition: and(succeeded(), ${{ not(parameters.skipNpmPublish) }}, eq(variables['NpmCustomFolderHasContent'], 'true')) - 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: '$(NpmCustomFolder)' - productstate: '$(NpmCustomTag)' - owners: 'vmorozov@microsoft.com' - approvers: 'khosany@microsoft.com' - - # Publish NPM packages using ESRP Release task with the "latest" tag. - - task: 'SFP.release-tasks.custom-build-release-task.EsrpRelease@10' - displayName: 'ESRP Release to npmjs.com (latest)' - condition: and(succeeded(), ${{ not(parameters.skipNpmPublish) }}, eq(variables['NpmLatestFolderHasContent'], 'true')) - 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: '$(NpmLatestFolder)' - productstate: 'latest' - owners: 'vmorozov@microsoft.com' - approvers: 'khosany@microsoft.com' - - # 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 - - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 - displayName: 📒 Generate Manifest Npm - inputs: - BuildDropPath: $(System.DefaultWorkingDirectory) - templateContext: outputs: - output: pipelineArtifact @@ -312,23 +181,23 @@ extends: 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 + # Run linting + - template: .ado/jobs/linting.yml@self + parameters: + buildEnvironment: Continuous + AgentPool: ${{ parameters.AgentPool }} + + # Create and sign Destop DLLs - ${{ each matrix in parameters.desktopBuildMatrix }}: - job: RnwNativeBuildDesktop${{ matrix.Name }} displayName: Build Desktop ${{ matrix.Name }} - dependsOn: RnwNpmPublish + dependsOn: SetVersionVars pool: ${{ parameters.AgentPool.Large }} timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout steps: + - template: .ado/templates/checkout-shallow.yml@self + - template: .ado/templates/prepare-js-env.yml@self - template: .ado/templates/prepare-build-env.yml@self @@ -345,10 +214,11 @@ extends: - template: .ado/templates/msbuild-sln.yml@self parameters: solutionDir: vnext - solutionName: ReactWindows-Desktop.sln + solutionName: ReactWindows-Desktop.Publish.slnf buildPlatform: ${{ matrix.BuildPlatform }} buildConfiguration: ${{ matrix.BuildConfiguration }} oneESMode: true ## Files are only copied to staging, not published + msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props - template: .ado/templates/publish-build-artifacts.yml@self parameters: @@ -360,14 +230,20 @@ extends: buildPlatform: ${{ matrix.BuildPlatform }} buildConfiguration: ${{ matrix.BuildConfiguration }} contents: | - React.Windows.Desktop\** - React.Windows.Desktop.DLL\** - React.Windows.Desktop.Test.DLL\** + React.Windows.Desktop\Microsoft.ReactNative.winmd + React.Windows.Desktop.DLL\react-native-win32.* - - template: .ado/templates/component-governance.yml@self + - 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: @@ -391,13 +267,16 @@ extends: artifactName: Desktop.${{matrix.buildPlatform}}.${{matrix.buildConfiguration}} targetPath: $(Build.StagingDirectory)/NuGet/Desktop/${{matrix.buildPlatform}}/${{matrix.buildConfiguration}} + # Create and sign Universal DLLs - ${{ each matrix in parameters.universalBuildMatrix }}: - job: RnwNativeBuildUniversal${{ matrix.Name }} displayName: Build Universal ${{ matrix.Name }} - dependsOn: RnwNpmPublish + dependsOn: SetVersionVars pool: ${{ parameters.AgentPool.Large }} timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout steps: + - template: .ado/templates/checkout-shallow.yml@self + - template: .ado/templates/prepare-js-env.yml@self - template: .ado/templates/prepare-build-env.yml@self @@ -421,6 +300,7 @@ extends: buildPlatform: ${{ matrix.BuildPlatform }} buildConfiguration: ${{ matrix.BuildConfiguration }} oneESMode: true ## Files are only copied to staging, not published + msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props - task: PowerShell@2 displayName: Make AnyCPU Reference Assemblies @@ -438,23 +318,23 @@ extends: buildPlatform: ${{ matrix.BuildPlatform }} buildConfiguration: ${{ matrix.BuildConfiguration }} contents: | - Microsoft.ReactNative\** - Microsoft.ReactNative.Managed\** + Microsoft.ReactNative\Microsoft.ReactNative.* + Microsoft.ReactNative.CsWinRT\Microsoft.ReactNative.Projection.* Microsoft.ReactNative.Managed.CodeGen\** - - template: .ado/templates/component-governance.yml@self - - # Make symbols available through http://symweb. - - task: PublishSymbols@2 - displayName: Publish symbols - env: - ARTIFACTSERVICES_SYMBOL_ACCOUNTNAME: microsoft - inputs: - SearchPattern: vnext/target/**/*.pdb - SymbolServerType: TeamServices + - 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: @@ -478,16 +358,18 @@ extends: artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} targetPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + # Create Nuget packages - job: RNWNuget + displayName: Pack NuGet dependsOn: - - RnwNpmPublish + - RnwNpmPack + - Linting - ${{ 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 + timeoutInMinutes: 60 # Protect against the long CodeSign task steps: - template: .ado/templates/checkout-shallow.yml@self @@ -496,7 +378,7 @@ extends: - 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) + # 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" @@ -506,13 +388,10 @@ extends: artifactName: ReactWindows publishCommitId: $(publishCommitId) npmVersion: $(npmVersion) - nugetroot: $(System.DefaultWorkingDirectory)\ReactWindows packMicrosoftReactNative: true packMicrosoftReactNativeCxx: true packMicrosoftReactNativeManaged: true packMicrosoftReactNativeManagedCodeGen: 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 @@ -532,13 +411,10 @@ extends: artifactName: ReactWindowsFabric publishCommitId: $(publishCommitId) npmVersion: $(npmVersion)-Fabric - nugetroot: $(System.DefaultWorkingDirectory)\ReactWindows packMicrosoftReactNative: true packMicrosoftReactNativeCxx: true # packMicrosoftReactNativeManaged: true # packMicrosoftReactNativeManagedCodeGen: 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 @@ -558,10 +434,7 @@ extends: artifactName: Desktop publishCommitId: $(publishCommitId) npmVersion: $(npmVersion) - nugetroot: $(System.DefaultWorkingDirectory)\Desktop 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 @@ -576,33 +449,16 @@ extends: - platform: ARM64EC configuration: Debug - - template: .ado/templates/prep-and-pack-nuget.yml@self + - template: .ado/templates/esrp-codesign-nuget.yml@self parameters: - artifactName: DesktopFabric - publishCommitId: $(publishCommitId) - npmVersion: $(npmVersion)-Fabric - nugetroot: $(System.DefaultWorkingDirectory)\Desktop - 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 + displayName: 'CodeSign all NuGet packages' + folderPath: $(System.DefaultWorkingDirectory)/NugetRootFinal + pattern: '**/*.nupkg' templateContext: sdl: binskim: - analyzeTargetGlob: '$(System.DefaultWorkingDirectory)\ReactWindows\**\Microsoft.ReactNative\Microsoft.ReactNative.dll;$(System.DefaultWorkingDirectory)\Desktop\**\React.Windows.Desktop.DLL\react-native-win32.dll' + 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' diff --git a/.ado/release.yml b/.ado/release.yml index 6312de40138..a824be29f46 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,16 @@ resources: trigger: branches: include: - - -1espublish + - main + - refs/heads/main + - '*-stable' + - 'refs/heads/*-stable' repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: @@ -23,84 +39,188 @@ extends: name: Azure-Pipelines-1ESPT-ExDShared image: windows-latest os: windows - customBuildTags: - - ES365AIMigrationTooling-Release stages: - - stage: PushToPrivateAdoStage - displayName: ADO - react-native + - stage: Release + displayName: Publish artifacts + # Allow manual runs unconditionally; for build-completion triggers, + # only proceed if the commit message starts with 'RELEASE:'. + condition: or(eq(variables['Build.Reason'], 'Manual'), startsWith(variables['Build.SourceVersionMessage'], 'RELEASE:')) jobs: - - job: PushPackages - displayName: Push packages - condition: succeeded() - timeoutInMinutes: 0 + - job: PushNpm + displayName: npmjs.com - Publish npm packages + variables: + - group: RNW Secrets + timeoutInMinutes: 30 templateContext: + type: releaseJob + isProduction: true inputs: - input: pipelineArtifact pipeline: 'Publish' - artifactName: 'ReactWindows-final-nuget' - targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + artifactName: 'NpmPackedTarballs' + targetPath: '$(Pipeline.Workspace)/published-packages' + - input: pipelineArtifact + pipeline: 'Publish' + artifactName: 'VersionEnvVars' + targetPath: '$(Pipeline.Workspace)/VersionEnvVars' steps: - - checkout: none - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' - - template: .ado/templates/authenticate-office-react-native-windows-bot.yml@self - task: CmdLine@2 - displayName: NuGet push (react-native) + displayName: Apply version variables inputs: - script: nuget.exe push *.nupkg -ApiKey $(oficeReactnativeWindowsBotAadAuthToken) -Source https://pkgs.dev.azure.com/ms/_packaging/react-native/nuget/v3/index.json -NonInteractive -Verbosity Detailed -SkipDuplicate -NoSymbols - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget - - stage: PushToPublicAdoStage - displayName: ADO - react-native-public - jobs: - - job: PushPackages - displayName: Push packages - condition: succeeded() - timeoutInMinutes: 0 + script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js + - script: dir /s "$(Pipeline.Workspace)\published-packages" + displayName: Show npm packages before cleanup + - 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: | + $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: 'EsrpRelease@11' + displayName: 'ESRP Release to npmjs.com' + condition: and(succeeded(), ne(variables['NpmDistTag'], ''), eq(variables['HasPackagesToPublish'], 'true')) + 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' + + - job: PushPrivateAdo + 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 ' - - template: .ado/templates/authenticate-office-react-native-windows-bot.yml@self - - task: CmdLine@2 - displayName: NuGet push (react-native-public) + - 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 - nuget - react-native-public + timeoutInMinutes: 30 + templateContext: + type: releaseJob + isProduction: true inputs: - script: nuget.exe push *.nupkg -ApiKey $(oficeReactnativeWindowsBotAadAuthToken) -Source https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json -NonInteractive -Verbosity Detailed -SkipDuplicate -NoSymbols - workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget - - stage: PushToNuGetStage - displayName: nuget.org - Push nuget packages - variables: - - group: RNW Secrets - jobs: - - job: PushPackages - displayName: Push packages - timeoutInMinutes: 0 + - input: pipelineArtifact + pipeline: 'Publish' + artifactName: 'ReactWindows-final-nuget' + targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + steps: + - 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: 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 ' + displayName: 'Use NuGet' - task: CmdLine@2 displayName: NuGet SetApiKey (nuget.org) inputs: script: nuget.exe SetApiKey $(nugetorg-apiKey-push) workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget - - task: PowerShell@2 + - 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: - 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 + - input: pipelineArtifact + pipeline: 'Publish' + artifactName: 'ReactWindows-final-nuget' + targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' + steps: + - powershell: | + # 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: + 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/npmGroupByTag.js b/.ado/scripts/npmGroupByTag.js deleted file mode 100644 index 23d6320e689..00000000000 --- a/.ado/scripts/npmGroupByTag.js +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env node -// @ts-check - -// Groups packed npm tarballs into tag-specific folders so ESRP can publish with the -// correct productstate value per tag. - -const fs = require('fs'); -const path = require('path'); - -/** - * @typedef {Object} PackageJsonBeachball - * @property {string | undefined} [defaultNpmTag] - */ - -/** - * @typedef {Object} PackageJson - * @property {string | undefined} [name] - * @property {string | undefined} [version] - * @property {boolean | undefined} [private] - * @property {PackageJsonBeachball | undefined} [beachball] - */ - -/** - * @returns {{packRootArg: string, customRootArg: string, latestRootArg: string}} - */ -function ensureArgs() { - const [, , packRootArg, customRootArg, latestRootArg] = process.argv; - if (!packRootArg || !customRootArg || !latestRootArg) { - console.error('Usage: node npmGroupByTag.js '); - process.exit(1); - } - return {packRootArg, customRootArg, latestRootArg}; -} - -/** - * @param {string} filePath - * @returns {unknown} - */ -function readJson(filePath) { - return JSON.parse(fs.readFileSync(filePath, 'utf8')); -} - -/** - * @param {string} pkgName - * @param {string} version - * @returns {string} - */ -function sanitizedTarballName(pkgName, version) { - const prefix = pkgName.startsWith('@') - ? pkgName.slice(1).replace(/\//g, '-').replace(/@/g, '-') - : pkgName.replace(/@/g, '-'); - return `${prefix}-${version}.tgz`; -} - -/** - * @param {string} tarballName - * @returns {string} - */ -function normalizePackedTarballName(tarballName) { - // beachball prefixes packed tarballs with a monotonically increasing number to avoid collisions - // when multiple packages share the same filename. Strip that prefix (single or repeated) for comparison. - return tarballName.replace(/^(?:\d+[._-])+/u, ''); -} - -/** - * @param {string} root - * @returns {string[]} - */ -function findPackageJsons(root) { - /** @type {string[]} */ - const results = []; - /** @type {string[]} */ - const stack = [root]; - - while (stack.length) { - const current = stack.pop(); - if (!current) { - continue; - } - /** @type {fs.Stats | undefined} */ - let stats; - try { - stats = fs.statSync(current); - } catch (e) { - continue; - } - - if (!stats.isDirectory()) { - continue; - } - - const entries = fs.readdirSync(current, {withFileTypes: true}); - for (const entry of entries) { - if (entry.name === 'node_modules' || entry.name === '.git') { - continue; - } - const entryPath = path.join(current, entry.name); - if (entry.isDirectory()) { - stack.push(entryPath); - } else if (entry.isFile() && entry.name === 'package.json') { - results.push(entryPath); - } - } - } - - return results; -} - -/** - * @param {string} name - * @param {string} value - */ -function setPipelineVariable(name, value) { - console.log(`##vso[task.setvariable variable=${name}]${value}`); -} - -(function main() { - const {packRootArg, customRootArg, latestRootArg} = ensureArgs(); - - const repoRoot = process.env.BUILD_SOURCESDIRECTORY || process.cwd(); - const packRoot = path.resolve(packRootArg); - const customRoot = path.resolve(customRootArg); - const latestRoot = path.resolve(latestRootArg); - - fs.mkdirSync(customRoot, {recursive: true}); - fs.mkdirSync(latestRoot, {recursive: true}); - - /** @type {string | null} */ - let customTag = null; - try { - const vnextPackageJson = /** @type {PackageJson} */ ( - readJson(path.join(repoRoot, 'vnext', 'package.json')) - ); - const tagFromVnext = vnextPackageJson?.beachball?.defaultNpmTag; - if (tagFromVnext && tagFromVnext !== 'latest') { - customTag = tagFromVnext; - } - } catch (e) { - console.warn('Unable to read vnext/package.json to determine custom tag.'); - } - - /** @type {string[]} */ - const tarballs = fs.existsSync(packRoot) - ? fs.readdirSync(packRoot).filter(file => file.endsWith('.tgz')) - : []; - - if (!tarballs.length) { - setPipelineVariable('NpmCustomTag', customTag || ''); - setPipelineVariable('NpmCustomFolder', customRoot); - setPipelineVariable('NpmCustomFolderHasContent', 'false'); - setPipelineVariable('NpmLatestFolder', latestRoot); - setPipelineVariable('NpmLatestFolderHasContent', 'false'); - return; - } - - /** @type {Set} */ - const customTarballs = new Set(); - - if (customTag) { - for (const packageJsonPath of findPackageJsons(repoRoot)) { - /** @type {PackageJson | undefined} */ - let pkg; - try { - pkg = /** @type {PackageJson} */ (readJson(packageJsonPath)); - } catch (e) { - continue; - } - - if (!pkg?.name || !pkg?.version) { - continue; - } - - const pkgTag = pkg?.beachball?.defaultNpmTag; - if (pkgTag === customTag && pkg.private !== true) { - customTarballs.add(sanitizedTarballName(pkg.name, pkg.version)); - } - } - } - - let customCount = 0; - let latestCount = 0; - - for (const tarball of tarballs) { - const sourcePath = path.join(packRoot, tarball); - const normalizedName = normalizePackedTarballName(tarball); - const destinationRoot = customTag && customTarballs.has(normalizedName) ? customRoot : latestRoot; - const destinationPath = path.join(destinationRoot, tarball); - fs.mkdirSync(path.dirname(destinationPath), {recursive: true}); - fs.renameSync(sourcePath, destinationPath); - if (destinationRoot === customRoot) { - customCount++; - } else { - latestCount++; - } - } - - setPipelineVariable('NpmCustomTag', customTag || ''); - setPipelineVariable('NpmCustomFolder', customRoot); - setPipelineVariable('NpmCustomFolderHasContent', customCount ? 'true' : 'false'); - setPipelineVariable('NpmLatestFolder', latestRoot); - setPipelineVariable('NpmLatestFolderHasContent', latestCount ? 'true' : 'false'); -})(); diff --git a/.ado/scripts/npmPack.js b/.ado/scripts/npmPack.js new file mode 100644 index 00000000000..5a3c141ac39 --- /dev/null +++ b/.ado/scripts/npmPack.js @@ -0,0 +1,500 @@ +#!/usr/bin/env node +// @ts-check + +/** + * npmPack.js - Pack all non-private workspace packages to tgz files + * + * Usage: + * node npmPack.js [targetDir] [--clean] [--check-npm] [--no-pack] + * + * Arguments: + * targetDir - Target directory for .tgz files (default: npm-pkgs in repo root) + * --clean - Clean target directory if it's not empty + * --check-npm - Check each package against npmjs.com and remove already published ones + * --no-pack - Skip packing, only check and clean target folder + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); +const { parseArgs } = require('util'); + +// ANSI color codes +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + gray: '\x1b[90m', +}; + +/** @type {boolean} */ +let useColors = true; + +/** + * Colorize text if colors are enabled + * @param {string} text - Text to colorize + * @param {string} color - Color code from colors object + * @returns {string} Colorized text + */ +function colorize(text, color) { + if (!useColors) { + return text; + } + return color + text + colors.reset; +} + +/** + * Display help information + */ +function showHelp() { + console.log(` +npmPack.js - Pack all non-private workspace packages to tgz files + +Usage: + node npmPack.js [options] [targetDir] + +Arguments: + targetDir Target directory for .tgz files + Default: npm-pkgs (in repository root) + +Options: + --clean Clean target directory if it's not empty + --check-npm Check each package against npmjs.com and remove already published ones + --no-pack Skip packing, only check and clean target folder + --no-color Disable colored output + --help, -h Show this help message + +Examples: + node npmPack.js + node npmPack.js --clean + node npmPack.js --check-npm + node npmPack.js --no-pack --check-npm + node npmPack.js path/to/output + node npmPack.js --clean --no-color path/to/output +`); +} + +/** + * Find the enlistment root by going up two directories from script location + * @returns {string} Repository root path + */ +function findEnlistmentRoot() { + const scriptDir = __dirname; + const repoRoot = path.resolve(scriptDir, '..', '..'); + + // Verify this is the repo root by checking for package.json + const packageJsonPath = path.join(repoRoot, 'package.json'); + if (!fs.existsSync(packageJsonPath)) { + throw new Error(`Could not find package.json at ${packageJsonPath}`); + } + + return repoRoot; +} + +/** + * Get workspace package paths from root package.json + * @param {string} repoRoot - Repository root directory + * @returns {string[]} Array of workspace patterns + */ +function getWorkspacePackages(repoRoot) { + const packageJsonPath = path.join(repoRoot, 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + if (!packageJson.workspaces || !packageJson.workspaces.packages) { + throw new Error('No workspaces.packages found in root package.json'); + } + + return packageJson.workspaces.packages; +} + +/** + * Recursively find all package.json files in a directory + * @param {string} dir - Directory to search + * @param {string[]} results - Accumulated results + * @returns {string[]} Array of package.json file paths + */ +function findPackageJsonsRecursive(dir, results = []) { + if (!fs.existsSync(dir)) { + return results; + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + // Skip node_modules directories + if (entry.name === 'node_modules') { + continue; + } + findPackageJsonsRecursive(fullPath, results); + } else if (entry.isFile() && entry.name === 'package.json') { + results.push(fullPath); + } + } + + return results; +} + +/** + * Match a pattern against a path + * Supports patterns like "packages/*" or "packages/@react-native-windows/*" + * @param {string} pattern - Workspace pattern to match + * @param {string} basePath - Base path to resolve pattern from + * @returns {string[]} Array of matching package.json paths + */ +function matchPattern(pattern, basePath) { + // Remove trailing /* if present + const cleanPattern = pattern.replace(/\/\*$/, ''); + const patternPath = path.join(basePath, cleanPattern); + + const results = []; + + // Check if pattern ends with /* + if (pattern.endsWith('/*')) { + // Pattern like "packages/*" - find all direct subdirectories + if (fs.existsSync(patternPath)) { + const entries = fs.readdirSync(patternPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + const packageJsonPath = path.join(patternPath, entry.name, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + results.push(packageJsonPath); + } + } + } + } + } else { + // Exact path - check if package.json exists + const packageJsonPath = path.join(patternPath, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + results.push(packageJsonPath); + } + } + + return results; +} + +/** + * Find all package.json files matching workspace patterns + * @param {string} repoRoot - Repository root directory + * @param {string[]} workspacePatterns - Array of workspace patterns + * @returns {string[]} Array of package.json file paths + */ +function findWorkspacePackageJsons(repoRoot, workspacePatterns) { + const packageJsonPaths = []; + + for (const pattern of workspacePatterns) { + const matches = matchPattern(pattern, repoRoot); + packageJsonPaths.push(...matches); + } + + return packageJsonPaths; +} + +/** + * Check if a package is private + * @param {string} packageJsonPath - Path to package.json file + * @returns {boolean} True if package is private + */ +function isPrivatePackage(packageJsonPath) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return packageJson.private === true; +} + +/** + * Check if a package version is published on npmjs.com + * @param {string} packageName - Name of the package + * @param {string} version - Version to check + * @returns {boolean} True if the package version is already published + */ +function isPublishedOnNpm(packageName, version) { + try { + // Use npm view to check if the specific version exists + execSync(`npm view ${packageName}@${version} version`, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + return true; + } catch (error) { + // If npm view fails, the version doesn't exist + return false; + } +} + +/** + * Extract package name and version from a .tgz file by reading its package.json + * @param {string} tgzPath - Full path to the .tgz file + * @returns {{name: string, version: string, error?: string} | null} Package info or null if extraction fails + */ +function getPackageInfoFromTgz(tgzPath) { + try { + // Convert Windows path to Unix-style path for tar command + // tar on Windows (via Git Bash) expects forward slashes + const unixPath = tgzPath.replace(/\\/g, '/'); + + // Use tar to extract package/package.json from the tarball + // The -xzf extracts from gzipped tar, the -O flag outputs to stdout, then the file to extract + const output = execSync(`tar -xzf "${unixPath}" -O package/package.json`, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + + const packageJson = JSON.parse(output); + return { + name: packageJson.name, + version: packageJson.version + }; + } catch (error) { + // If extraction fails, return error information + const message = error instanceof Error ? error.message : String(error); + return { name: '', version: '', error: message }; + } +} + +/** + * Check and remove already published packages from target directory + * @param {string} targetDir - Directory containing .tgz files + * @returns {{checked: number, removed: number}} Statistics about checked and removed packages + */ +function checkAndRemovePublishedPackages(targetDir) { + let checkedCount = 0; + let removedCount = 0; + + if (!fs.existsSync(targetDir)) { + return { checked: checkedCount, removed: removedCount }; + } + + const files = fs.readdirSync(targetDir); + const tgzFiles = files.filter(f => f.endsWith('.tgz')); + + console.log(`\n${colorize('Checking packages against npmjs.com...', colors.bright)}`); + + for (const tgzFile of tgzFiles) { + const tgzPath = path.join(targetDir, tgzFile); + const packageInfo = getPackageInfoFromTgz(tgzPath); + + if (!packageInfo || packageInfo.error || !packageInfo.name) { + const errorMsg = packageInfo?.error || 'cannot extract package.json'; + console.log(` ${colorize('⚠', colors.yellow)} Skipping ${tgzFile}`); + console.log(` ${colorize('Error:', colors.red)} ${errorMsg}`); + continue; + } + + checkedCount++; + console.log(` Checking ${colorize(packageInfo.name, colors.cyan)}@${colorize(packageInfo.version, colors.dim)}...`); + + if (isPublishedOnNpm(packageInfo.name, packageInfo.version)) { + fs.rmSync(tgzPath); + console.log(` ${colorize('✓', colors.green)} Already published - removed ${tgzFile}`); + removedCount++; + } else { + console.log(` ${colorize('→', colors.blue)} Not published - keeping ${tgzFile}`); + } + } + + return { checked: checkedCount, removed: removedCount }; +} + +/** + * Pack a package using npm pack + * @param {string} packageDir - Directory containing the package + * @param {string} targetDir - Directory to output .tgz file + * @returns {boolean} True if packing succeeded + */ +function packPackage(packageDir, targetDir) { + const packageJsonPath = path.join(packageDir, 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + const packageName = packageJson.name; + + console.log(`Packing ${colorize(packageName, colors.cyan)}...`); + + try { + // Run npm pack in the package directory, output to target directory + const output = execSync(`npm pack --pack-destination "${targetDir}"`, { + cwd: packageDir, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + + const tgzFileName = output.trim().split('\n').pop(); + console.log(` ${colorize('✓', colors.green)} Created ${tgzFileName}`); + return true; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(` ${colorize('✗', colors.red)} Failed to pack ${colorize(packageName, colors.cyan)}: ${message}`); + return false; + } +} + +/** + * Main function + */ +function main() { + // Parse command line arguments + /** @type {import('util').ParseArgsConfig['options']} */ + const options = { + help: { + type: 'boolean', + short: 'h', + default: false, + }, + clean: { + type: 'boolean', + default: false, + }, + 'check-npm': { + type: 'boolean', + default: false, + }, + 'no-pack': { + type: 'boolean', + default: false, + }, + 'no-color': { + type: 'boolean', + default: false, + }, + }; + + let args; + try { + args = parseArgs({ + options, + allowPositionals: true, + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`${colorize('Error parsing arguments:', colors.red)} ${message}`); + console.error('Use --help for usage information'); + process.exit(1); + } + + // Show help if requested + if (args.values.help) { + showHelp(); + process.exit(0); + } + + // Set color mode (colors enabled by default, disabled if --no-color is passed) + useColors = !args.values['no-color']; + + const cleanFlag = args.values.clean; + const checkNpmFlag = args.values['check-npm']; + const noPackFlag = args.values['no-pack']; + const targetDirArg = args.positionals[0]; + + try { + // 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 + ? (repoRoot ? path.resolve(repoRoot, targetDirArg) : path.resolve(targetDirArg)) + : path.join(/** @type {string} */ (repoRoot), 'npm-pkgs'); + + console.log(`${colorize('Target directory:', colors.bright)} ${targetDir}`); + + // Handle target directory + if (fs.existsSync(targetDir)) { + const files = fs.readdirSync(targetDir); + if (files.length > 0) { + // Only enforce clean directory requirement if we're packing + if (!noPackFlag && !cleanFlag) { + console.error(`${colorize('Error:', colors.red)} Target directory is not empty: ${targetDir}`); + console.error('Use --clean flag to clean the directory before packing'); + process.exit(1); + } + + if (cleanFlag) { + console.log(`${colorize('Cleaning target directory...', colors.yellow)}`); + for (const file of files) { + fs.rmSync(path.join(targetDir, file), { recursive: true, force: true }); + } + } + } + } else { + console.log(`${colorize('Creating target directory...', colors.yellow)}`); + fs.mkdirSync(targetDir, { recursive: true }); + } + + // Pack non-private packages (unless --no-pack is specified) + let packedCount = 0; + let skippedCount = 0; + let failedCount = 0; + + if (!noPackFlag) { + // Get workspace packages + const workspacePatterns = getWorkspacePackages(repoRoot); + console.log(`${colorize('Workspace patterns:', colors.bright)} ${workspacePatterns.join(', ')}`); + + // Find all package.json files + const packageJsonPaths = findWorkspacePackageJsons(repoRoot, workspacePatterns); + console.log(`${colorize('Found', colors.bright)} ${colorize(packageJsonPaths.length.toString(), colors.cyan)} ${colorize('workspace packages', colors.bright)}\n`); + + for (const packageJsonPath of packageJsonPaths) { + const packageDir = path.dirname(packageJsonPath); + + if (isPrivatePackage(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + console.log(`${colorize('Skipping private package:', colors.gray)} ${colorize(packageJson.name, colors.dim)}`); + skippedCount++; + continue; + } + + const success = packPackage(packageDir, targetDir); + if (success) { + packedCount++; + } else { + failedCount++; + } + } + + console.log(`\n${colorize('✓', colors.green)} ${colorize('Packing complete:', colors.bright)}`); + console.log(` ${colorize('Packed:', colors.bright)} ${colorize(packedCount.toString(), colors.green)}`); + console.log(` ${colorize('Skipped (private):', colors.bright)} ${colorize(skippedCount.toString(), colors.gray)}`); + console.log(` ${colorize('Failed:', colors.bright)} ${failedCount > 0 ? colorize(failedCount.toString(), colors.red) : colorize(failedCount.toString(), colors.green)}`); + console.log(` ${colorize('Target:', colors.bright)} ${targetDir}`); + } else { + console.log(`\n${colorize('Skipping packing (--no-pack specified)', colors.yellow)}`); + } + + // Check and remove already published packages if requested + let checkedCount = 0; + let removedCount = 0; + + if (checkNpmFlag) { + const result = checkAndRemovePublishedPackages(targetDir); + checkedCount = result.checked; + removedCount = result.removed; + + console.log(`\n${colorize('✓', colors.green)} ${colorize('NPM check complete:', colors.bright)}`); + console.log(` ${colorize('Checked:', colors.bright)} ${colorize(checkedCount.toString(), colors.cyan)}`); + console.log(` ${colorize('Removed (already published):', colors.bright)} ${colorize(removedCount.toString(), colors.green)}`); + console.log(` ${colorize('Remaining:', colors.bright)} ${colorize((checkedCount - removedCount).toString(), colors.cyan)}`); + } + + if (failedCount > 0) { + process.exit(1); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`${colorize('Error:', colors.red)} ${message}`); + process.exit(1); + } +} + +// Run main function +main(); diff --git a/.ado/scripts/setVersionEnvVars.js b/.ado/scripts/setVersionEnvVars.js index 0d026245cc9..63358f259a9 100644 --- a/.ado/scripts/setVersionEnvVars.js +++ b/.ado/scripts/setVersionEnvVars.js @@ -37,6 +37,11 @@ const versionEnvVars = { publishCommitId: commitId, reactDevDependency: pkgJson.devDependencies['react'], reactNativeDevDependency: pkgJson.devDependencies['react-native'], + npmDistTag: pkgJson?.beachball?.defaultNpmTag?.trim(), +} + +if (!versionEnvVars.npmDistTag) { + throw new Error('defaultNpmTag is missing in vnext/package.json'); } // Set the build number so the build in the publish pipeline and the release pipeline are named with the convenient version @@ -54,8 +59,14 @@ console.log(`##vso[task.setvariable variable=npmVersion]${versionEnvVars.npmVers console.log(`##vso[task.setvariable variable=publishCommitId]${versionEnvVars.publishCommitId}`); console.log(`##vso[task.setvariable variable=reactDevDependency]${versionEnvVars.reactDevDependency}`); console.log(`##vso[task.setvariable variable=reactNativeDevDependency]${versionEnvVars.reactNativeDevDependency}`); +console.log(`##vso[task.setvariable variable=NpmDistTag]${versionEnvVars.npmDistTag}`); + +const runnerTemp = process.env.RUNNER_TEMP; +if (!runnerTemp) { + throw new Error('RUNNER_TEMP environment variable is not set'); +} -const dirPath = path.resolve(process.env.RUNNER_TEMP, 'versionEnvVars'); +const dirPath = path.resolve(runnerTemp, 'versionEnvVars'); fs.mkdirSync(dirPath, {recursive: true}); fs.writeFileSync(path.resolve(dirPath, 'versionEnvVars.js'), @@ -68,4 +79,5 @@ console.log("##vso[task.setvariable variable=npmVersion]${versionEnvVars.npmVers console.log("##vso[task.setvariable variable=publishCommitId]${versionEnvVars.publishCommitId}"); console.log("##vso[task.setvariable variable=reactDevDependency]${versionEnvVars.reactDevDependency}"); console.log("##vso[task.setvariable variable=reactNativeDevDependency]${versionEnvVars.reactNativeDevDependency}"); +console.log("##vso[task.setvariable variable=NpmDistTag]${versionEnvVars.npmDistTag}"); `); diff --git a/.ado/templates/authenticate-office-react-native-windows-bot.yml b/.ado/templates/authenticate-office-react-native-windows-bot.yml deleted file mode 100644 index ab43399a15d..00000000000 --- a/.ado/templates/authenticate-office-react-native-windows-bot.yml +++ /dev/null @@ -1,11 +0,0 @@ -steps: - - task: AzureCLI@2 - inputs: - azureSubscription: 'Office-React-Native-Windows-Bot' - scriptType: 'bash' - scriptLocation: 'inlineScript' - inlineScript: | - # Note that the resource is specified to limit the token to Azure DevOps - aadToken=$(az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv) - echo "##vso[task.setvariable variable=oficeReactnativeWindowsBotAadAuthToken;issecret=true]$aadToken" - displayName: 'Generate oficeReactnativeWindowsBotAadAuthToken AAD token using Azure CLI' \ No newline at end of file diff --git a/.ado/templates/checkout-shallow.yml b/.ado/templates/checkout-shallow.yml index aac2b5372b2..12478e6b9fc 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 persistCredentials: true submodules: false 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/prep-and-pack-nuget.yml b/.ado/templates/prep-and-pack-nuget.yml index 2ec496b0913..b01b99206d6 100644 --- a/.ado/templates/prep-and-pack-nuget.yml +++ b/.ado/templates/prep-and-pack-nuget.yml @@ -32,9 +32,6 @@ parameters: type: boolean default: false - - name: signMicrosoft - type: boolean - default: false steps: - pwsh: | @@ -107,8 +104,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) @@ -118,7 +113,6 @@ steps: outputPackage: Microsoft.ReactNative.Cxx packageVersion: ${{parameters.npmVersion}} buildProperties: CommitId=${{parameters.publishCommitId}};nugetroot=${{parameters.nugetroot}};baseconfiguration=$(baseConfiguration);baseplatform=$(basePlatform) - codesignNuget: ${{ parameters.signMicrosoft }} - ${{ if eq(parameters.packMicrosoftReactNativeManaged, true) }}: - ${{ if containsValue(parameters.slices.*.configuration, 'Debug') }}: diff --git a/.ado/templates/prep-and-pack-single.yml b/.ado/templates/prep-and-pack-single.yml index bff59700824..fb81d234475 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,33 +12,17 @@ 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: - ${{ if ne(parameters.slices, '') }}: @@ -51,50 +35,8 @@ steps: displayName: '${{ parameters.outputPackage }} - Strip slices from nuspec' workingDirectory: $(System.DefaultWorkingDirectory)/ReactWindows - - ${{ 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)/ReactWindows - # 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 @@ -106,39 +48,6 @@ 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 displayName: List files in NugetRootFinal 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..48c38c92364 --- /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" + +- powershell: | + 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/verdaccio-start.yml b/.ado/templates/verdaccio-start.yml index 294219a8016..6326c25d357 100644 --- a/.ado/templates/verdaccio-start.yml +++ b/.ado/templates/verdaccio-start.yml @@ -17,7 +17,20 @@ steps: - template: compute-beachball-branch-name.yml - ${{ if eq(parameters.beachballPublish, true) }}: - - script: npx beachball publish --branch origin/$(BeachBallBranchName) --no-push --registry http://localhost:4873 --yes --verbose --access public --changehint "Run `yarn change` from root of repo to generate a change file." + - script: npx --yes beachball bump --branch origin/$(BeachBallBranchName) --no-push --yes --verbose --changehint "Run `yarn change` from root of repo to generate a change file." + displayName: Beachball bump versions + + - script: node .ado/scripts/npmPack.js --clean --no-color + displayName: Pack all workspace packages + + - script: node .ado/scripts/npmPack.js --no-pack --check-npm --no-color + displayName: Remove already published packages + + - script: | + for %%f in (npm-pkgs\*.tgz) do ( + echo Publishing %%f to verdaccio... + npm publish "%%f" --registry http://localhost:4873 --access public + ) displayName: Publish packages to verdaccio - script: yarn config set registry http://localhost:4873 diff --git a/.gitignore b/.gitignore index dec24164761..a327b902af4 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ UpgradeLog.htm *.userprefs #Tooling +tools/ _ReSharper*/ *.resharper [Tt]est[Rr]esult* @@ -190,3 +191,5 @@ nul # midgard-yarn-strict .store*/* + +/npm-pkgs diff --git a/package.json b/package.json index 8c22dbbdf45..e2bb65e58a7 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,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": "*", diff --git a/packages/@rnw-scripts/prepare-release/.eslintrc.js b/packages/@rnw-scripts/prepare-release/.eslintrc.js new file mode 100644 index 00000000000..35e0d115126 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['@rnw-scripts'], + parserOptions: {tsconfigRootDir : __dirname}, +}; diff --git a/packages/@rnw-scripts/prepare-release/.gitignore b/packages/@rnw-scripts/prepare-release/.gitignore new file mode 100644 index 00000000000..f42efbb9f7c --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/.gitignore @@ -0,0 +1,2 @@ +lib/ +lib-commonjs/ diff --git a/packages/@rnw-scripts/prepare-release/bin.js b/packages/@rnw-scripts/prepare-release/bin.js new file mode 100644 index 00000000000..aae8dc76ae9 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/bin.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * @format + */ + +require('source-map-support').install(); +require('./lib-commonjs/prepareRelease'); diff --git a/packages/@rnw-scripts/prepare-release/package.json b/packages/@rnw-scripts/prepare-release/package.json new file mode 100644 index 00000000000..065835f4adb --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/package.json @@ -0,0 +1,46 @@ +{ + "name": "@rnw-scripts/prepare-release", + "version": "0.0.1", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/react-native-windows", + "directory": "packages/@rnw-scripts/prepare-release" + }, + "scripts": { + "build": "rnw-scripts build", + "clean": "rnw-scripts clean", + "lint": "rnw-scripts lint", + "lint:fix": "rnw-scripts lint:fix", + "watch": "rnw-scripts watch" + }, + "main": "lib-commonjs/prepareRelease.js", + "bin": { + "prepare-release": "./bin.js" + }, + "dependencies": { + "@react-native-windows/find-repo-root": "^0.0.0-canary.99", + "@react-native-windows/fs": "^0.0.0-canary.70", + "@react-native-windows/package-utils": "^0.0.0-canary.96", + "source-map-support": "^0.5.19" + }, + "devDependencies": { + "@rnw-scripts/eslint-config": "1.2.38", + "@rnw-scripts/just-task": "2.3.58", + "@rnw-scripts/ts-config": "2.0.6", + "@types/node": "^22.14.0", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "eslint": "^8.19.0", + "prettier": "2.8.8", + "typescript": "5.0.4" + }, + "files": [ + "bin.js", + "lib-commonjs" + ], + "engines": { + "node": ">= 22" + } +} diff --git a/packages/@rnw-scripts/prepare-release/src/beachballBump.ts b/packages/@rnw-scripts/prepare-release/src/beachballBump.ts new file mode 100644 index 00000000000..8658d308d5e --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/beachballBump.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Beachball change file detection and version bump invocation. + * + * @format + */ + +import fs from '@react-native-windows/fs'; +import path from 'path'; + +import {exec} from './proc'; + +/** + * Check whether there are pending beachball change files in the repo. + * + * Beachball stores change files as JSON in the `change/` directory at the + * repo root. If there are any .json files there, there are pending changes. + */ +export function hasChangeFiles(repoRoot: string): boolean { + const changeDir = path.join(repoRoot, 'change'); + if (!fs.existsSync(changeDir)) { + return false; + } + + const entries = fs.readdirSync(changeDir); + return entries.some(entry => entry.endsWith('.json')); +} + +/** + * Run beachball bump to consume change files and update versions/changelogs. + * + * Invokes: npx beachball bump --branch / --yes --verbose + * + * The --yes flag suppresses prompts. + * The --verbose flag provides detailed output. + * The --branch flag tells beachball the baseline branch to diff against. + */ +export async function bumpVersions(opts: { + targetBranch: string; + remote: string; + cwd: string; +}): Promise { + await exec( + `npx beachball bump --branch ${opts.remote}/${opts.targetBranch} --yes --verbose`, + {cwd: opts.cwd}, + ); +} diff --git a/packages/@rnw-scripts/prepare-release/src/git.ts b/packages/@rnw-scripts/prepare-release/src/git.ts new file mode 100644 index 00000000000..8afe4cc9b44 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/git.ts @@ -0,0 +1,128 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Git operations module. Provides a typed wrapper around git CLI commands. + * Simplified from fork-sync/src/modules/git.ts. + * + * @format + */ + +import {spawn} from './proc'; + +/** + * Typed wrapper around a git repository directory. + * All methods execute git commands with cwd set to the wrapped directory. + */ +export class GitRepo { + readonly dir: string; + + constructor(dir: string) { + this.dir = dir; + } + + /** Fetch from a remote. */ + async fetch(remote: string): Promise { + await this.git('fetch', remote); + } + + /** Checkout an existing ref (branch, tag, or commit). */ + async checkout(ref: string): Promise { + await this.git('checkout', ref); + } + + /** + * Create or reset a branch to a start point. + * Uses `git checkout -B` which creates the branch if it doesn't exist, + * or resets it if it does. + */ + async checkoutNewBranch(name: string, startPoint?: string): Promise { + const args = ['checkout', '-B', name]; + if (startPoint) { + args.push(startPoint); + } + await this.git(...args); + } + + /** Stage all changes (git add --all). */ + async stageAll(): Promise { + await this.git('add', '--all'); + } + + /** Create a commit with the given message. */ + async commit(message: string): Promise { + await this.git('commit', '-m', message); + } + + /** Push a branch to a remote, optionally with --force. */ + async push( + remote: string, + branch: string, + opts?: {force?: boolean}, + ): Promise { + const args = ['push', remote, branch]; + if (opts?.force) { + args.push('--force'); + } + await this.git(...args); + } + + /** Resolve a ref to its SHA (git rev-parse). */ + async revParse(ref: string): Promise { + return this.git('rev-parse', ref); + } + + /** Get the current branch name, or 'HEAD' if in detached state. */ + async currentBranch(): Promise { + return this.git('rev-parse', '--abbrev-ref', 'HEAD'); + } + + /** Check for uncommitted changes (git status --porcelain). */ + async statusPorcelain(pathspec?: string): Promise { + const args = ['status', '--porcelain']; + if (pathspec) { + args.push('--', pathspec); + } + return this.git(...args); + } + + /** List all remotes with their URLs (git remote -v). */ + async remoteList(): Promise { + return this.git('remote', '-v'); + } + + /** Git log with format and optional range. */ + async log(opts: {format: string; range?: string}): Promise { + const args = ['log', `--format=${opts.format}`]; + if (opts.range) { + args.push(opts.range); + } + return this.git(...args); + } + + /** Get file names changed between two refs, optionally filtered by path. */ + async diffNameOnly( + ref1: string, + ref2?: string, + pathspec?: string, + ): Promise { + const args = ['diff', '--name-only', ref1]; + if (ref2) { + args.push(ref2); + } + if (pathspec) { + args.push('--', pathspec); + } + return this.git(...args); + } + + /** Check if a branch exists on a remote. Returns the ls-remote output or empty. */ + async lsRemote(remote: string, branch: string): Promise { + return this.git('ls-remote', '--heads', remote, `refs/heads/${branch}`); + } + + /** Internal helper: run git with the repo's cwd. */ + private async git(...args: string[]): Promise { + return spawn('git', args, {cwd: this.dir}); + } +} diff --git a/packages/@rnw-scripts/prepare-release/src/github.ts b/packages/@rnw-scripts/prepare-release/src/github.ts new file mode 100644 index 00000000000..07ad6e264ae --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/github.ts @@ -0,0 +1,139 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * GitHub operations via the gh CLI. + * + * @format + */ + +import fs from '@react-native-windows/fs'; +import os from 'os'; +import path from 'path'; + +import {exec} from './proc'; + +export interface PRInfo { + number: number; + url: string; +} + +/** + * Run a callback with a temporary file containing the given content. + * The file is cleaned up after the callback completes (success or failure). + */ +async function withTempFile( + content: string, + fn: (filePath: string) => Promise, +): Promise { + const tmpFile = path.join( + os.tmpdir(), + `gh-pr-body-${process.pid}-${Date.now()}.md`, + ); + fs.writeFileSync(tmpFile, content, 'utf8'); + try { + return await fn(tmpFile); + } finally { + try { + fs.unlinkSync(tmpFile); + } catch { + // Ignore cleanup errors + } + } +} + +/** + * Find an existing open PR by head branch name. + * Returns null if no matching PR exists. + */ +export async function findPR(opts: { + head: string; + cwd: string; + repo?: string; +}): Promise { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + const result = await exec( + `gh pr list --head "${opts.head}" --json number,url --limit 1${repoFlag}`, + {cwd: opts.cwd, fallback: '[]'}, + ); + + let prs: Array<{number: number; url: string}>; + try { + prs = JSON.parse(result); + } catch { + return null; + } + + if (!Array.isArray(prs) || prs.length === 0) { + return null; + } + + return {number: prs[0].number, url: prs[0].url}; +} + +/** + * Create a new pull request. Returns the PR number and URL. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +export async function createPR(opts: { + head: string; + base: string; + title: string; + body: string; + cwd: string; + repo?: string; +}): Promise { + return withTempFile(opts.body, async bodyFile => { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + const result = await exec( + `gh pr create --head "${opts.head}" --base "${opts.base}"` + + ` --title "${escapeForShell(opts.title)}"` + + ` --body-file "${bodyFile}"${repoFlag}`, + {cwd: opts.cwd}, + ); + + // gh pr create outputs the PR URL on success + const url = result.trim(); + const match = url.match(/\/pull\/(\d+)/); + const number = match ? parseInt(match[1], 10) : 0; + + return {number, url}; + }); +} + +/** + * Update an existing pull request's body text. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +export async function updatePR(opts: { + number: number; + title: string; + body: string; + cwd: string; + repo?: string; +}): Promise { + await withTempFile(opts.body, async bodyFile => { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + await exec( + `gh pr edit ${opts.number} --title "${escapeForShell(opts.title)}"` + + ` --body-file "${bodyFile}"${repoFlag}`, + {cwd: opts.cwd}, + ); + }); +} + +/** + * Escape a string for safe inclusion in a double-quoted shell argument. + * Used for single-line values like titles; multiline content uses --body-file. + */ +function escapeForShell(str: string): string { + return str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\$/g, '\\$') + .replace(/`/g, '\\`'); +} diff --git a/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts b/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts new file mode 100644 index 00000000000..0b247386978 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts @@ -0,0 +1,397 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * This script automates the version bump PR workflow. It checks for pending + * beachball change files, bumps versions, and creates or updates a + * "Version Packages" pull request. + * + * Usage: + * npx prepare-release --branch [--dry-run] [--no-color] [--help] + * + * @format + */ + +import {parseArgs} from 'node:util'; +import fs from '@react-native-windows/fs'; +import path from 'path'; + +import findRepoRoot from '@react-native-windows/find-repo-root'; + +import {GitRepo} from './git'; +import {findPR, createPR, updatePR} from './github'; +import {hasChangeFiles, bumpVersions} from './beachballBump'; +import { + collectBumpedPackages, + generatePRBody, + generateConsoleSummary, +} from './releaseSummary'; + +// --------------------------------------------------------------------------- +// Color utilities (from npmPack.js pattern) +// --------------------------------------------------------------------------- + +const ansi = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + gray: '\x1b[90m', +}; + +let useColors = true; + +function colorize(text: string, color: string): string { + if (!useColors) { + return text; + } + return color + text + ansi.reset; +} + +// --------------------------------------------------------------------------- +// CLI help +// --------------------------------------------------------------------------- + +function showHelp(): void { + console.log(` +prepare-release - Automate version bump PRs using beachball + +Usage: + npx prepare-release --branch [options] + +Options: + --branch Target branch to prepare release for (required) + --dry-run Do everything except push and PR create/update + --no-color Disable colored output + --help, -h Show this help message + +Examples: + npx prepare-release --branch main + npx prepare-release --branch 0.76-stable --dry-run +`); +} + +// --------------------------------------------------------------------------- +// Remote detection +// --------------------------------------------------------------------------- + +/** + * Normalize a git URL to a canonical form for comparison. + * + * Handles SSH (git@github.com:org/repo.git), HTTPS, with/without .git suffix. + * Output: lowercase "github.com/org/repo" + */ +function normalizeGitUrl(url: string): string { + let normalized = url; + + // Strip trailing .git + normalized = normalized.replace(/\.git$/, ''); + + // Convert SSH format: git@github.com:org/repo -> github.com/org/repo + normalized = normalized.replace(/^git@([^:]+):/, '$1/'); + + // Convert HTTPS format: https://github.com/org/repo -> github.com/org/repo + normalized = normalized.replace(/^https?:\/\//, ''); + + // Lowercase for case-insensitive comparison + normalized = normalized.toLowerCase(); + + // Strip trailing slash + normalized = normalized.replace(/\/$/, ''); + + return normalized; +} + +/** + * Extract "owner/repo" from a git URL for use with `gh --repo`. + * + * E.g. "git@github.com:microsoft/react-native-windows.git" + * -> "microsoft/react-native-windows" + */ +function extractGitHubRepo(url: string): string { + const normalized = normalizeGitUrl(url); + // normalized is like "github.com/owner/repo" + const match = normalized.match(/github\.com\/(.+)/); + if (!match) { + throw new Error(`Could not extract GitHub owner/repo from "${url}"`); + } + return match[1]; +} + +/** + * Detect which git remote matches the repository URL from package.json. + * + * Parses `git remote -v` output and matches against the normalized repo URL. + * On CI this is typically "origin"; on developer machines it may differ. + */ +async function detectRemote(git: GitRepo, repoUrl: string): Promise { + const canonical = normalizeGitUrl(repoUrl); + const remoteOutput = await git.remoteList(); + + for (const line of remoteOutput.split('\n')) { + // Lines: "origin\thttps://github.com/microsoft/react-native-windows.git (fetch)" + const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/); + if (!match) { + continue; + } + const remoteName = match[1]; + const remoteUrl = match[2]; + + if (normalizeGitUrl(remoteUrl) === canonical) { + return remoteName; + } + } + + throw new Error( + `Could not find a git remote matching "${repoUrl}". ` + + 'Run "git remote -v" and verify the repository URL in root package.json.', + ); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +(async () => { + // 1. Parse CLI arguments + const {values} = parseArgs({ + options: { + branch: {type: 'string'}, + 'dry-run': {type: 'boolean', default: false}, + help: {type: 'boolean', short: 'h', default: false}, + 'no-color': {type: 'boolean', default: false}, + }, + }); + + if (values.help) { + showHelp(); + process.exit(0); + } + + useColors = !values['no-color']; + + const targetBranch = values.branch; + if (!targetBranch) { + console.error(colorize('Error: --branch is required', ansi.red)); + showHelp(); + process.exit(1); + } + + const dryRun = values['dry-run']; + + if (dryRun) { + console.log(colorize('[DRY RUN MODE]', ansi.yellow)); + } + + try { + // 2. Find repo root + const repoRoot = await findRepoRoot(); + console.log(`${colorize('Repository root:', ansi.bright)} ${repoRoot}`); + + // 3. Read repository URL from root package.json + const rootPkgJsonPath = path.join(repoRoot, 'package.json'); + const rootPkgJson = JSON.parse(fs.readFileSync(rootPkgJsonPath, 'utf8')); + const repoUrl: string = rootPkgJson.repository?.url ?? ''; + + if (!repoUrl) { + throw new Error('Could not find repository.url in root package.json'); + } + console.log(`${colorize('Repository URL:', ansi.bright)} ${repoUrl}`); + + // 4. Detect git remote name + const git = new GitRepo(repoRoot); + const remoteName = await detectRemote(git, repoUrl); + console.log(`${colorize('Git remote:', ansi.bright)} ${remoteName}`); + + // 4b. Extract GitHub owner/repo for gh CLI --repo flag + const githubRepo = extractGitHubRepo(repoUrl); + console.log(`${colorize('GitHub repo:', ansi.bright)} ${githubRepo}`); + + // 5. Fetch from remote + console.log(colorize(`Fetching from ${remoteName}...`, ansi.dim)); + await git.fetch(remoteName); + + // 6. Check for pending change files + console.log(colorize('Checking for change files...', ansi.dim)); + if (!hasChangeFiles(repoRoot)) { + console.log( + colorize('No pending change files found. Nothing to do.', ansi.green), + ); + process.exit(0); + } + console.log(colorize('Found pending change files.', ansi.green)); + + // 7. Check for existing PR + const prBranch = `prepare-release/${targetBranch}`; + console.log( + colorize(`Looking for existing PR from ${prBranch}...`, ansi.dim), + ); + const existingPR = await findPR({ + head: prBranch, + cwd: repoRoot, + repo: githubRepo, + }); + + if (existingPR) { + console.log( + `${colorize('Found existing PR:', ansi.bright)} #${ + existingPR.number + } (${existingPR.url})`, + ); + } else { + console.log(colorize('No existing PR found. Will create one.', ansi.dim)); + } + + // 8. Save original branch so we can restore it when done + const originalBranch = await git.currentBranch(); + console.log(colorize(`Saving current branch: ${originalBranch}`, ansi.dim)); + + try { + // 9. Create/reset the prepare-release branch from target branch HEAD + console.log( + colorize( + `Creating branch ${prBranch} from ${remoteName}/${targetBranch}...`, + ansi.dim, + ), + ); + await git.checkoutNewBranch(prBranch, `${remoteName}/${targetBranch}`); + + // 10. Run beachball bump + console.log(colorize('Running beachball bump...', ansi.bright)); + await bumpVersions({ + targetBranch, + remote: remoteName, + cwd: repoRoot, + }); + + // 11. Check if beachball actually changed anything + const status = await git.statusPorcelain(); + if (!status) { + console.log( + colorize( + 'beachball bump made no changes. Nothing to commit.', + ansi.yellow, + ), + ); + process.exit(0); + } + + // 12. Collect bumped package info for PR description + // Parse changed package.json paths from git status --porcelain output + const changedFiles = status + .split('\n') + .map(line => line.trim().split(/\s+/).pop()!) + .filter(f => f.endsWith('package.json')); + + const bumpedPackages = collectBumpedPackages(changedFiles, repoRoot); + console.log(generateConsoleSummary(bumpedPackages)); + + // 13. Stage all + commit + const commitMessage = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`; + console.log(colorize(`Committing: "${commitMessage}"...`, ansi.dim)); + await git.stageAll(); + await git.commit(commitMessage); + + // 14. Force-push the branch + if (dryRun) { + console.log( + colorize( + `[DRY RUN] Would force-push ${prBranch} to ${remoteName}`, + ansi.yellow, + ), + ); + } else { + console.log( + colorize(`Force-pushing ${prBranch} to ${remoteName}...`, ansi.dim), + ); + await git.push(remoteName, prBranch, {force: true}); + + // Verify the branch landed on the remote + const lsRemoteOut = await git.lsRemote(remoteName, prBranch); + if (!lsRemoteOut) { + throw new Error( + `Push verification failed: branch "${prBranch}" not found on ` + + `"${remoteName}" after push. Check your push permissions.`, + ); + } + console.log( + colorize( + `Push verified: ${prBranch} exists on ${remoteName}`, + ansi.green, + ), + ); + } + + // 15. Create or update PR + const prTitle = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`; + const prBody = generatePRBody(targetBranch, bumpedPackages); + + if (existingPR) { + if (dryRun) { + console.log( + colorize( + `[DRY RUN] Would update PR #${existingPR.number}`, + ansi.yellow, + ), + ); + } else { + console.log( + colorize(`Updating PR #${existingPR.number}...`, ansi.dim), + ); + await updatePR({ + number: existingPR.number, + title: prTitle, + body: prBody, + cwd: repoRoot, + repo: githubRepo, + }); + console.log( + `${colorize('PR updated:', ansi.green)} ${existingPR.url}`, + ); + } + } else { + if (dryRun) { + console.log( + colorize(`[DRY RUN] Would create PR: "${prTitle}"`, ansi.yellow), + ); + } else { + console.log(colorize('Creating pull request...', ansi.dim)); + const newPR = await createPR({ + head: prBranch, + base: targetBranch, + title: prTitle, + body: prBody, + cwd: repoRoot, + repo: githubRepo, + }); + console.log(`${colorize('PR created:', ansi.green)} ${newPR.url}`); + } + } + + // 16. Done + console.log(''); + console.log(colorize('Done!', ansi.green + ansi.bright)); + + if (dryRun) { + console.log(colorize('\nPR body that would be used:', ansi.dim)); + console.log(prBody); + } + } finally { + // Always restore the original branch + if (originalBranch && originalBranch !== 'HEAD') { + console.log( + colorize(`Restoring original branch: ${originalBranch}`, ansi.dim), + ); + await git.checkout(originalBranch); + } + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`${colorize('Error:', ansi.red)} ${message}`); + process.exit(1); + } +})(); diff --git a/packages/@rnw-scripts/prepare-release/src/proc.ts b/packages/@rnw-scripts/prepare-release/src/proc.ts new file mode 100644 index 00000000000..da5a19913e2 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/proc.ts @@ -0,0 +1,120 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Async wrapper around Node.js child_process.spawn. + * Simplified from fork-sync/src/modules/proc.ts. + * + * @format + */ + +import {spawn as nodeSpawn} from 'child_process'; + +/** + * Error thrown when a spawned process exits with a non-zero code. + */ +export class ExecError extends Error { + readonly command: string; + readonly args: readonly string[]; + readonly cwd: string | undefined; + readonly exitCode: number | null; + readonly stderr: string; + + constructor(opts: { + command: string; + args: readonly string[]; + cwd?: string; + exitCode: number | null; + stderr: string; + }) { + const cmdStr = [opts.command, ...opts.args].join(' '); + super( + opts.stderr || + `Command failed with exit code ${opts.exitCode}: ${cmdStr}`, + ); + this.name = 'ExecError'; + this.command = opts.command; + this.args = opts.args; + this.cwd = opts.cwd; + this.exitCode = opts.exitCode; + this.stderr = opts.stderr; + } +} + +export interface SpawnOpts { + cwd?: string; + /** If set, return this value instead of throwing on non-zero exit */ + fallback?: string; + /** Extra environment variables (merged with process.env) */ + env?: Record; +} + +/** + * Spawn a command (no shell) and return its trimmed stdout. + * Throws ExecError on non-zero exit unless `fallback` is provided. + */ +export function spawn( + command: string, + args: readonly string[], + opts?: SpawnOpts, +): Promise { + return spawnImpl(command, [...args], opts, false); +} + +/** + * Execute a command string in a shell and return its trimmed stdout. + * Uses shell mode, needed for .cmd shims on Windows (npx, gh). + */ +export function exec(command: string, opts?: SpawnOpts): Promise { + return spawnImpl(command, [], opts, true); +} + +function spawnImpl( + command: string, + args: string[], + opts: SpawnOpts | undefined, + shell: boolean, +): Promise { + return new Promise((resolve, reject) => { + const child = nodeSpawn(command, args, { + cwd: opts?.cwd, + env: opts?.env ? {...process.env, ...opts.env} : undefined, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + shell, + }); + + const stdoutChunks: Buffer[] = []; + const stderrChunks: Buffer[] = []; + + child.stdout!.on('data', (chunk: Buffer) => stdoutChunks.push(chunk)); + child.stderr!.on('data', (chunk: Buffer) => stderrChunks.push(chunk)); + + child.on('error', err => { + reject(err); + }); + + child.on('close', exitCode => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trimEnd(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trimEnd(); + + if (exitCode !== 0) { + if (opts?.fallback !== undefined) { + resolve(opts.fallback); + } else { + reject( + new ExecError({ + command, + args, + cwd: opts?.cwd, + exitCode, + stderr, + }), + ); + } + } else { + resolve(stdout); + } + }); + }); +} diff --git a/packages/@rnw-scripts/prepare-release/src/releaseSummary.ts b/packages/@rnw-scripts/prepare-release/src/releaseSummary.ts new file mode 100644 index 00000000000..d368497b62c --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/src/releaseSummary.ts @@ -0,0 +1,139 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Parse bumped packages and generate PR description markdown. + * + * @format + */ + +import fs from '@react-native-windows/fs'; +import path from 'path'; + +export interface BumpedPackage { + name: string; + version: string; + comments: Array<{comment: string; author: string}>; +} + +/** + * Collect information about packages that were bumped by beachball. + * + * For each changed package.json, reads the new version and parses + * CHANGELOG.json for the latest changelog entry. + * + * @param changedPackageJsonPaths Relative paths to package.json files + * that were modified by beachball bump (from git status --porcelain) + * @param repoRoot The repository root directory + */ +export function collectBumpedPackages( + changedPackageJsonPaths: string[], + repoRoot: string, +): BumpedPackage[] { + const bumped: BumpedPackage[] = []; + + for (const relPath of changedPackageJsonPaths) { + const fullPath = path.join(repoRoot, relPath); + if (!fs.existsSync(fullPath)) { + continue; + } + + let pkgJson: {name?: string; version?: string}; + try { + pkgJson = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + } catch { + continue; + } + + const name = pkgJson.name; + const version = pkgJson.version; + if (!name || !version) { + continue; + } + + // Try to read CHANGELOG.json from the same directory + const pkgDir = path.dirname(fullPath); + const changelogPath = path.join(pkgDir, 'CHANGELOG.json'); + const comments: Array<{comment: string; author: string}> = []; + + if (fs.existsSync(changelogPath)) { + try { + const changelog = JSON.parse(fs.readFileSync(changelogPath, 'utf8')); + + // CHANGELOG.json structure: + // { entries: [{ version, comments: { : [{ comment, author }] } }] } + const latest = changelog.entries?.[0]; + if (latest && latest.version === version) { + for (const typeComments of Object.values(latest.comments || {})) { + for (const c of typeComments as Array<{ + comment: string; + author: string; + }>) { + comments.push({ + comment: c.comment || '', + author: c.author || '', + }); + } + } + } + } catch { + // If CHANGELOG.json is malformed, skip comments + } + } + + bumped.push({name, version, comments}); + } + + // Sort by package name for consistent output + bumped.sort((a, b) => a.name.localeCompare(b.name)); + return bumped; +} + +/** + * Generate the pull request body markdown. + */ +export function generatePRBody( + targetBranch: string, + packages: BumpedPackage[], +): string { + const lines: string[] = []; + + lines.push( + 'This PR was auto-generated by `prepare-release`. ' + + `When ready to release, merge this PR into \`${targetBranch}\`. ` + + 'If not ready yet, this PR will be updated as more changes merge.', + ); + lines.push(''); + lines.push(`## Packages to Release (${packages.length})`); + lines.push(''); + + for (const pkg of packages) { + lines.push(`### ${pkg.name}@${pkg.version}`); + if (pkg.comments.length > 0) { + for (const c of pkg.comments) { + lines.push(`- ${c.comment}`); + } + } else { + lines.push('- *(dependency update)*'); + } + lines.push(''); + } + + return lines.join('\n'); +} + +/** + * Generate a console-friendly summary of bumped packages. + */ +export function generateConsoleSummary(packages: BumpedPackage[]): string { + if (packages.length === 0) { + return 'No packages were bumped.'; + } + + const lines: string[] = []; + lines.push(`Bumped ${packages.length} package(s):`); + for (const pkg of packages) { + lines.push(` ${pkg.name} => ${pkg.version}`); + } + return lines.join('\n'); +} diff --git a/packages/@rnw-scripts/prepare-release/tsconfig.json b/packages/@rnw-scripts/prepare-release/tsconfig.json new file mode 100644 index 00000000000..c62faa78baf --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@rnw-scripts/ts-config", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/vnext/PropertySheets/CIBuildOptimizations.props b/vnext/PropertySheets/CIBuildOptimizations.props new file mode 100644 index 00000000000..61d0244f890 --- /dev/null +++ b/vnext/PropertySheets/CIBuildOptimizations.props @@ -0,0 +1,29 @@ + + + + + false + false + false + + + + false + false + %(AdditionalOptions) /MP + + + diff --git a/vnext/ReactWindows-Desktop.Publish.slnf b/vnext/ReactWindows-Desktop.Publish.slnf new file mode 100644 index 00000000000..0261a90d017 --- /dev/null +++ b/vnext/ReactWindows-Desktop.Publish.slnf @@ -0,0 +1,14 @@ +{ + "solution": { + "path": "ReactWindows-Desktop.sln", + "projects": [ + "Common\\Common.vcxproj", + "Desktop\\React.Windows.Desktop.vcxproj", + "Desktop.DLL\\React.Windows.Desktop.DLL.vcxproj", + "Folly\\Folly.vcxproj", + "FollyWin32\\FollyWin32.vcxproj", + "ReactCommon\\ReactCommon.vcxproj", + "fmt\\fmt.vcxproj" + ] + } +} diff --git a/yarn.lock b/yarn.lock index 43f30763344..6ae6de8f691 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,11 +113,25 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + "@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.20.0", "@babel/core@^7.22.0": version "7.24.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" @@ -139,6 +153,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.25.2": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/eslint-parser@^7.20.0": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz#e27eee93ed1d271637165ef3a86e2b9332395c32" @@ -148,6 +183,15 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" +"@babel/eslint-parser@^7.25.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz#6a294a4add732ebe7ded8a8d2792dd03dd81dc3f" + integrity sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA== + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.1" + "@babel/generator@^7.20.0", "@babel/generator@^7.24.1", "@babel/generator@^7.7.2": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" @@ -158,6 +202,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -183,6 +238,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6": version "7.23.10" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz#25d55fafbaea31fd0e723820bb6cc3df72edf7ea" @@ -257,6 +323,11 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.23.0" +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" @@ -285,6 +356,14 @@ dependencies: "@babel/types" "^7.24.0" +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/helper-module-transforms@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" @@ -296,6 +375,15 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -361,16 +449,31 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + "@babel/helper-wrap-function@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" @@ -389,6 +492,14 @@ "@babel/traverse" "^7.24.1" "@babel/types" "^7.24.0" +"@babel/helpers@^7.28.6": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.2.tgz#9cfbccb02b8e229892c0b07038052cc1a8709c49" + integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + "@babel/highlight@^7.24.2": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" @@ -404,6 +515,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== +"@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.2.tgz#58bd50b9a7951d134988a1ae177a35ef9a703ba1" + integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== + dependencies: + "@babel/types" "^7.29.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" @@ -1284,6 +1402,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@babel/traverse@^7.11.5", "@babel/traverse@^7.20.0", "@babel/traverse@^7.23.2", "@babel/traverse@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" @@ -1300,6 +1427,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.11.5", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" @@ -1309,6 +1449,14 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1500,11 +1648,23 @@ dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.10.0": version "4.11.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== +"@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + "@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" @@ -1815,6 +1975,14 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.12": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -1824,6 +1992,14 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.24" +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" @@ -1852,6 +2028,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": version "0.3.22" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" @@ -1868,6 +2049,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -2420,6 +2609,34 @@ resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.6.1.tgz#3b20ddd1385fab0487db103dc6519570f8892e6d" integrity sha512-oJftvmLOj6Y6/bF4kPcK6L83yNBALGmqNYugf94BzP0FQGpHBwimVN2ygqkQ2Sn2ZU3pGUZMs0jV6+Gku2GyYg== +"@react-native-windows/find-repo-root@^0.0.0-canary.99": + version "0.0.0-canary.99" + resolved "https://registry.yarnpkg.com/@react-native-windows/find-repo-root/-/find-repo-root-0.0.0-canary.99.tgz#9aba0d034c7bbe0de7108b38bb5e352ec8c6cbf2" + integrity sha512-ks88JuTE/EmV9etG0QpxXo96lgnYe5q5RjbtW0aboLy93djKVb+P95xrr7psk15TzcQGTM063iJrKVabRjLhNg== + dependencies: + "@react-native-windows/fs" "^0.0.0-canary.70" + find-up "^4.1.0" + minimatch "^10.0.3" + +"@react-native-windows/fs@^0.0.0-canary.70": + version "0.0.0-canary.70" + resolved "https://registry.yarnpkg.com/@react-native-windows/fs/-/fs-0.0.0-canary.70.tgz#53165cc8f0310be7aebb1bda669ff54ae2298ada" + integrity sha512-o4DY6n31140d+8ylusqzFYM69ReJ3J56i5vAZw3Pq0CtoELyloOSCHBUNmNFTZKnOeU+3RDZ2hH/nv4Vf8lstA== + dependencies: + graceful-fs "^4.2.8" + minimatch "^10.0.3" + +"@react-native-windows/package-utils@^0.0.0-canary.96": + version "0.0.0-canary.96" + resolved "https://registry.yarnpkg.com/@react-native-windows/package-utils/-/package-utils-0.0.0-canary.96.tgz#545e53dabd788203d6daa2c050df6cea3836ed0b" + integrity sha512-FQ8c0vxVhIVOjmru+8CPliEwhRx+WPDFcskf4eu/cSiucW3n1LmBGsZaLkae02FHOl2yioT9hW1L7X5YDj5xIA== + dependencies: + "@react-native-windows/find-repo-root" "^0.0.0-canary.99" + "@react-native-windows/fs" "^0.0.0-canary.70" + get-monorepo-packages "^1.2.0" + lodash "^4.17.15" + minimatch "^10.0.3" + "@react-native/assets-registry@0.74.89": version "0.74.89" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.74.89.tgz#18b15e8bbd74720c5996a46d592f4c7038eaaa06" @@ -2561,11 +2778,34 @@ eslint-plugin-react-hooks "^4.6.0" eslint-plugin-react-native "^4.0.0" +"@react-native/eslint-config@0.82.0-nightly-20250806-5936f29d6": + version "0.82.0-nightly-20250806-5936f29d6" + resolved "https://registry.yarnpkg.com/@react-native/eslint-config/-/eslint-config-0.82.0-nightly-20250806-5936f29d6.tgz#b10d372254735683891f16de1b430b89ea47abd5" + integrity sha512-3bBcXPGjwAGSIUYW3Z+fcyPOoiLbIXF+QAe7qO7hDMz7NxlA6bJ7njaMTmryJhTuWX382DXu/XkVV15GjAiMMQ== + dependencies: + "@babel/core" "^7.25.2" + "@babel/eslint-parser" "^7.25.1" + "@react-native/eslint-plugin" "0.82.0-nightly-20250806-5936f29d6" + "@typescript-eslint/eslint-plugin" "^8.36.0" + "@typescript-eslint/parser" "^8.36.0" + eslint-config-prettier "^8.5.0" + eslint-plugin-eslint-comments "^3.2.0" + eslint-plugin-ft-flow "^2.0.1" + eslint-plugin-jest "^27.9.0" + eslint-plugin-react "^7.30.1" + eslint-plugin-react-hooks "^5.2.0" + eslint-plugin-react-native "^4.0.0" + "@react-native/eslint-plugin@0.74.89": version "0.74.89" resolved "https://registry.yarnpkg.com/@react-native/eslint-plugin/-/eslint-plugin-0.74.89.tgz#81b4811032684eb25cabb9537ad3dedaf2c84497" integrity sha512-k8j7UPC4UcHLCrWpN5my5x7xBdj1j0IBBRCBqWJm+kRBdTQAKHuN05OZ/fQpnjoKPzXXFLYs71olROKXdM+KQQ== +"@react-native/eslint-plugin@0.82.0-nightly-20250806-5936f29d6": + version "0.82.0-nightly-20250806-5936f29d6" + resolved "https://registry.yarnpkg.com/@react-native/eslint-plugin/-/eslint-plugin-0.82.0-nightly-20250806-5936f29d6.tgz#5800835a7c357066810b0452ebe7e4f4c955ea31" + integrity sha512-Xi6Wcy+VY6Vup69LfIQQpFX27UlcyTTvroHXkAUtxPPJcj3g5BwlVIo3koLWaOGadrXWFxnaSRmYDdpK4QY8Ag== + "@react-native/gradle-plugin@0.74.89": version "0.74.89" resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.74.89.tgz#740075e8dc17e66a1c66cab6b19912e8c36a58ea" @@ -2616,6 +2856,56 @@ invariant "^2.2.4" nullthrows "^1.1.1" +"@rnw-scripts/babel-node-config@2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@rnw-scripts/babel-node-config/-/babel-node-config-2.3.3.tgz#109486f50ae37abbf181edced3e7252429f0ddf8" + integrity sha512-/ryz3rfANywNGoEVGi54GeAB0plJJYMfjwiNAPV42EzcjYbbJjDvzg8Z/EbP7zgs5XcBiSHN7yE9dCK70Uj7Bw== + +"@rnw-scripts/eslint-config@1.2.38": + version "1.2.38" + resolved "https://registry.yarnpkg.com/@rnw-scripts/eslint-config/-/eslint-config-1.2.38.tgz#1a0044fd8a4ddb538cd91efdb0cf6c83a25efe29" + integrity sha512-BdGhf0TWkhoL9HqgGQwVhpCOr4DrhtlDmF+tHLZu5nRC1aR9p/frM+BoJVlY3g2Vezu5lN513h0oU8O8b09H3w== + dependencies: + "@babel/core" "^7.25.2" + "@babel/eslint-parser" "^7.25.1" + "@microsoft/eslint-plugin-sdl" "^0.2.0" + "@react-native/eslint-config" "0.82.0-nightly-20250806-5936f29d6" + eslint-config-prettier "^8.5.0" + eslint-plugin-ft-flow "^2.0.1" + hermes-eslint "0.23.1" + +"@rnw-scripts/jest-e2e-config@1.4.12": + version "1.4.12" + resolved "https://registry.yarnpkg.com/@rnw-scripts/jest-e2e-config/-/jest-e2e-config-1.4.12.tgz#c923e780b7a84adf9da6369befc8937629168110" + integrity sha512-jvxE79tjfHG++HQXCPVC3Rp/EZ6IDboQsCCGvJfPIhH/MDObmXZGhHf9s7C5yxKM3/eVQezbMmHXUG4n2902GQ== + dependencies: + "@rnw-scripts/babel-node-config" "2.3.3" + +"@rnw-scripts/jest-unittest-config@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@rnw-scripts/jest-unittest-config/-/jest-unittest-config-1.5.12.tgz#f9520859950ac188552b394f1230a11ef83dc416" + integrity sha512-NC5Arkb6NVSgMtHCfJ8282X87B0dkAhYXZslwqfhV979VbRB/muDcqifkm+TgbQB3CHJi5emXKc7xq/A2TeoZg== + dependencies: + "@rnw-scripts/babel-node-config" "2.3.3" + +"@rnw-scripts/just-task@2.3.58": + version "2.3.58" + resolved "https://registry.yarnpkg.com/@rnw-scripts/just-task/-/just-task-2.3.58.tgz#a5e4e6192a218086f94f74dc0cc1abcf209185d7" + integrity sha512-tLyjF0xwrl0mJjc4ymRjbLvPpC06U0X56ctan71QHdO6MP9XPKGtI9rH4RiLrlST2Gc91VO15nqedODvkHN0lg== + dependencies: + "@octokit/rest" "^18.5.3" + "@rnw-scripts/jest-e2e-config" "1.4.12" + "@rnw-scripts/jest-unittest-config" "1.5.12" + depcheck "^1.4.1" + find-up "^4.1.0" + glob "^7.1.6" + just-scripts "^1.3.3" + +"@rnw-scripts/ts-config@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@rnw-scripts/ts-config/-/ts-config-2.0.6.tgz#b45c2dd8c5414bc88ec3af03fffd0a6ea621dc42" + integrity sha512-hytYtCDrf++emgyNCtNwQFAkvIyBCBOfBWpIAgi0HYUljDDIYTt+NBw6wzrA2v4Ge5YhzGIqa0keUUDgAh9tbw== + "@rnx-kit/align-deps@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@rnx-kit/align-deps/-/align-deps-2.4.0.tgz#612340b21f8d6f5c20408314d6c92f1d18395008" @@ -3110,6 +3400,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^22.14.0": + version "22.19.15" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.15.tgz#6091d99fdf7c08cb57dc8b1345d407ba9a1df576" + integrity sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg== + dependencies: + undici-types "~6.21.0" + "@types/npm-package-arg@*": version "6.1.4" resolved "https://registry.yarnpkg.com/@types/npm-package-arg/-/npm-package-arg-6.1.4.tgz#112b74a61cb8632313f600212782840156e0228e" @@ -3385,6 +3682,20 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" +"@typescript-eslint/eslint-plugin@^8.36.0": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz#ddfdfb30f8b5ccee7f3c21798b377c51370edd55" + integrity sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.57.1" + "@typescript-eslint/type-utils" "8.57.1" + "@typescript-eslint/utils" "8.57.1" + "@typescript-eslint/visitor-keys" "8.57.1" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.4.0" + "@typescript-eslint/parser@^5.21.0", "@typescript-eslint/parser@^5.30.0", "@typescript-eslint/parser@^5.57.1": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" @@ -3406,6 +3717,26 @@ "@typescript-eslint/visitor-keys" "7.15.0" debug "^4.3.4" +"@typescript-eslint/parser@^8.36.0": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.1.tgz#d523e559b148264055c0a49a29d5f50c7de659c2" + integrity sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw== + dependencies: + "@typescript-eslint/scope-manager" "8.57.1" + "@typescript-eslint/types" "8.57.1" + "@typescript-eslint/typescript-estree" "8.57.1" + "@typescript-eslint/visitor-keys" "8.57.1" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.1.tgz#16af9fe16eedbd7085e4fdc29baa73715c0c55c5" + integrity sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.57.1" + "@typescript-eslint/types" "^8.57.1" + debug "^4.4.3" + "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" @@ -3422,6 +3753,19 @@ "@typescript-eslint/types" "7.15.0" "@typescript-eslint/visitor-keys" "7.15.0" +"@typescript-eslint/scope-manager@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz#4524d7e7b420cb501807499684d435ae129aaf35" + integrity sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg== + dependencies: + "@typescript-eslint/types" "8.57.1" + "@typescript-eslint/visitor-keys" "8.57.1" + +"@typescript-eslint/tsconfig-utils@8.57.1", "@typescript-eslint/tsconfig-utils@^8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz#9233443ec716882a6f9e240fd900a73f0235f3d7" + integrity sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg== + "@typescript-eslint/type-utils@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" @@ -3442,6 +3786,17 @@ debug "^4.3.4" ts-api-utils "^1.3.0" +"@typescript-eslint/type-utils@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz#c49af1347b5869ca85155547a8f34f84ab386fd9" + integrity sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA== + dependencies: + "@typescript-eslint/types" "8.57.1" + "@typescript-eslint/typescript-estree" "8.57.1" + "@typescript-eslint/utils" "8.57.1" + debug "^4.4.3" + ts-api-utils "^2.4.0" + "@typescript-eslint/types@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" @@ -3452,6 +3807,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== +"@typescript-eslint/types@8.57.1", "@typescript-eslint/types@^8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.1.tgz#54b27a8a25a7b45b4f978c3f8e00c4c78f11142c" + integrity sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ== + "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" @@ -3479,6 +3839,21 @@ semver "^7.6.0" ts-api-utils "^1.3.0" +"@typescript-eslint/typescript-estree@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz#a9fd28d4a0ec896aa9a9a7e0cead62ea24f99e76" + integrity sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g== + dependencies: + "@typescript-eslint/project-service" "8.57.1" + "@typescript-eslint/tsconfig-utils" "8.57.1" + "@typescript-eslint/types" "8.57.1" + "@typescript-eslint/visitor-keys" "8.57.1" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" + "@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.30.0", "@typescript-eslint/utils@^5.47.1": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -3503,6 +3878,16 @@ "@typescript-eslint/types" "7.15.0" "@typescript-eslint/typescript-estree" "7.15.0" +"@typescript-eslint/utils@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.1.tgz#e40f5a7fcff02fd24092a7b52bd6ec029fb50465" + integrity sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.57.1" + "@typescript-eslint/types" "8.57.1" + "@typescript-eslint/typescript-estree" "8.57.1" + "@typescript-eslint/visitor-keys@5.62.0", "@typescript-eslint/visitor-keys@^5.42.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" @@ -3519,6 +3904,14 @@ "@typescript-eslint/types" "7.15.0" eslint-visitor-keys "^3.4.3" +"@typescript-eslint/visitor-keys@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz#3af4f88118924d3be983d4b8ae84803f11fe4563" + integrity sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A== + dependencies: + "@typescript-eslint/types" "8.57.1" + eslint-visitor-keys "^5.0.0" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -4421,6 +4814,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + bare-events@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.0.tgz#a7a7263c107daf8b85adf0b64f908503454ab26e" @@ -4431,6 +4829,11 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +baseline-browser-mapping@^2.9.0: + version "2.10.8" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz#23d1cea1a85b181c2b8660b6cfe626dc2fb15630" + integrity sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ== + basic-ftp@^5.0.2: version "5.0.4" resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.4.tgz#28aeab7bfbbde5f5d0159cd8bb3b8e633bbb091d" @@ -4495,6 +4898,13 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" +brace-expansion@^5.0.2: + version "5.0.4" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" + integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== + dependencies: + balanced-match "^4.0.2" + braces@^3.0.2, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -4512,6 +4922,17 @@ browserslist@^4.22.2, browserslist@^4.23.0: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +browserslist@^4.24.0: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + bs-logger@0.x: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" @@ -4656,6 +5077,11 @@ caniuse-lite@^1.0.30001587: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz#a0bce920155fa56a1885a69c74e1163fc34b4881" integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== +caniuse-lite@^1.0.30001759: + version "1.0.30001780" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz#0e413de292808868a62ed9118822683fa120a110" + integrity sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ== + cardinal@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" @@ -5286,6 +5712,13 @@ debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, de dependencies: ms "2.1.2" +debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -5559,6 +5992,11 @@ electron-to-chromium@^1.4.668: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz#1f077d9a095761804aec7ec6346c3f4b69b56534" integrity sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw== +electron-to-chromium@^1.5.263: + version "1.5.313" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz#193e9ae2c2ab6915acb41e833068381e4ef0b3e4" + integrity sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA== + emitter-listener@^1.0.1, emitter-listener@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" @@ -5838,6 +6276,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -5963,6 +6406,11 @@ eslint-plugin-react-hooks@^4.6.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== +eslint-plugin-react-hooks@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" + integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== + eslint-plugin-react-native-globals@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" @@ -6082,6 +6530,11 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + eslint@^8.17.0, eslint@^8.19.0, eslint@^8.23.1: version "8.57.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" @@ -6384,6 +6837,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -7074,11 +7532,25 @@ hermes-eslint@0.19.1: hermes-estree "0.19.1" hermes-parser "0.19.1" +hermes-eslint@0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.23.1.tgz#e0801e58bd4a70f01b0b0659805f315ab7ea6691" + integrity sha512-DaEpbJobK1KwpTSXrPIKkHs2h+B+RTw2F1g9S70tjtJ14a3zM+2gPVUtc8xyffQqRJ6tPfs+/zRKwV17lwDvqA== + dependencies: + esrecurse "^4.3.0" + hermes-estree "0.23.1" + hermes-parser "0.23.1" + hermes-estree@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.19.1.tgz#d5924f5fac2bf0532547ae9f506d6db8f3c96392" integrity sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g== +hermes-estree@0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.23.1.tgz#d0bac369a030188120ee7024926aabe5a9f84fdb" + integrity sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg== + hermes-parser@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.19.1.tgz#1044348097165b7c93dc198a80b04ed5130d6b1a" @@ -7086,6 +7558,13 @@ hermes-parser@0.19.1: dependencies: hermes-estree "0.19.1" +hermes-parser@0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.23.1.tgz#e5de648e664f3b3d84d01b48fc7ab164f4b68205" + integrity sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA== + dependencies: + hermes-estree "0.23.1" + hermes-profile-transformer@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz#bd0f5ecceda80dd0ddaae443469ab26fb38fc27b" @@ -7269,6 +7748,11 @@ ignore@^5.0.5, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + image-size@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.1.1.tgz#ddd67d4dc340e52ac29ce5f546a09f4e29e840ac" @@ -8285,6 +8769,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" @@ -9154,6 +9643,13 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== +minimatch@^10.0.3, minimatch@^10.2.2: + version "10.2.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== + dependencies: + brace-expansion "^5.0.2" + minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -9303,7 +9799,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0: +ms@2.1.3, ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -9427,6 +9923,11 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.27: + version "2.0.36" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" + integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== + node-stream-zip@^1.9.1: version "1.15.0" resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" @@ -9971,11 +10472,21 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" @@ -10857,6 +11368,11 @@ semver@^7.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +semver@^7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + semver@~7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -11541,6 +12057,14 @@ through@^2.3.6, through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -11612,6 +12136,11 @@ ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== + ts-jest@^29.0.3: version "29.1.2" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" @@ -11852,6 +12381,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -11927,6 +12461,14 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" From d39649af1daa6efb9dda2beea42117017981a424 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 17 Mar 2026 15:28:24 -0700 Subject: [PATCH 2/4] Change files --- ...ative-windows-b21b1aa4-5186-49a0-a66d-8f3dd13ac69e.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-b21b1aa4-5186-49a0-a66d-8f3dd13ac69e.json diff --git a/change/react-native-windows-b21b1aa4-5186-49a0-a66d-8f3dd13ac69e.json b/change/react-native-windows-b21b1aa4-5186-49a0-a66d-8f3dd13ac69e.json new file mode 100644 index 00000000000..17761e9a4c7 --- /dev/null +++ b/change/react-native-windows-b21b1aa4-5186-49a0-a66d-8f3dd13ac69e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Apply publish and release pipelines improvements", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch" +} From 54956ef635b845deac2f4a4e9ce8a27ab427b5b1 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 17 Mar 2026 17:16:17 -0700 Subject: [PATCH 3/4] Fix Linter issues --- packages/@office-iss/react-native-win32/.flowconfig | 4 ++++ packages/@rnw-scripts/prepare-release/.prettierignore | 1 + vnext/.flowconfig | 4 ++++ 3 files changed, 9 insertions(+) create mode 100644 packages/@rnw-scripts/prepare-release/.prettierignore diff --git a/packages/@office-iss/react-native-win32/.flowconfig b/packages/@office-iss/react-native-win32/.flowconfig index ee3508127e6..c9bc2a291bb 100644 --- a/packages/@office-iss/react-native-win32/.flowconfig +++ b/packages/@office-iss/react-native-win32/.flowconfig @@ -55,6 +55,10 @@ .*/node_modules/sample-apps/.* .*/node_modules/playground/.* +; Ignore prepare-release's own node_modules (hermes-estree .flow files use unsupported syntax) +.*/node_modules/@rnw-scripts/prepare-release/node_modules/.* +.*/packages/@rnw-scripts/prepare-release/node_modules/.* + ; Ignore templates for 'react-native init' /template/.* diff --git a/packages/@rnw-scripts/prepare-release/.prettierignore b/packages/@rnw-scripts/prepare-release/.prettierignore new file mode 100644 index 00000000000..938df8077a0 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/.prettierignore @@ -0,0 +1 @@ +lib-commonjs diff --git a/vnext/.flowconfig b/vnext/.flowconfig index e764283c7be..4664bfc9d15 100644 --- a/vnext/.flowconfig +++ b/vnext/.flowconfig @@ -53,6 +53,10 @@ .*/node_modules/sample-apps/.* .*/node_modules/playground/.* +; Ignore prepare-release's own node_modules (hermes-estree .flow files use unsupported syntax) +.*/node_modules/@rnw-scripts/prepare-release/node_modules/.* +.*/packages/@rnw-scripts/prepare-release/node_modules/.* + ; Ignore templates for 'react-native init' /packages/react-native/template/.* From d8c0e72e53befa43d76b460193502a7025128c0b Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 17 Mar 2026 17:30:06 -0700 Subject: [PATCH 4/4] Change files --- ...-native-win32-b77b5491-2e55-469b-a2d2-493d421f8a2e.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@office-iss-react-native-win32-b77b5491-2e55-469b-a2d2-493d421f8a2e.json diff --git a/change/@office-iss-react-native-win32-b77b5491-2e55-469b-a2d2-493d421f8a2e.json b/change/@office-iss-react-native-win32-b77b5491-2e55-469b-a2d2-493d421f8a2e.json new file mode 100644 index 00000000000..d9569fc016e --- /dev/null +++ b/change/@office-iss-react-native-win32-b77b5491-2e55-469b-a2d2-493d421f8a2e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fix Linter issues", + "packageName": "@office-iss/react-native-win32", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch" +}