From ed91fc03c9dfa1934bbdc75210c0c5e6d6323307 Mon Sep 17 00:00:00 2001 From: "Deepak Rathore (ALLYIS INC)" Date: Thu, 21 Aug 2025 23:06:25 +0530 Subject: [PATCH 01/12] feat: roslyn versio bump --- azure-pipelines/roslyn-version-bump.yml | 355 ++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 azure-pipelines/roslyn-version-bump.yml diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml new file mode 100644 index 0000000000..103e0ba46e --- /dev/null +++ b/azure-pipelines/roslyn-version-bump.yml @@ -0,0 +1,355 @@ +trigger: none + +parameters: + - name: createPullRequest + displayName: Create Pull Request + type: boolean + default: true + - name: targetBranch + displayName: Target Branch for PR + type: string + default: main + +resources: + pipelines: + - pipeline: officialBuildCI + source: 327 + project: internal + branch: refs/heads/main + trigger: none + +pool: + vmImage: ubuntu-latest + +variables: + - name: RoslynStartSHA + value: "" + - name: RoslynEndSHA + value: $(resources.pipeline.officialBuildCI.sourceCommit) + - name: RoslynBuildNumber + value: $(resources.pipeline.officialBuildCI.runName) + - name: RoslynBuildId + value: $(resources.pipeline.officialBuildCI.runID) + - name: RoslynVersion + value: "" + +stages: + - stage: BumpRoslyn + displayName: Bump Roslyn Version + jobs: + - job: ProcessBump + displayName: Process Roslyn Bump + steps: + - checkout: self + persistCredentials: true + + - download: officialBuildCI + artifact: AssetManifests + displayName: Download AssetManifests from Roslyn build + + - task: UseDotNet@2 + displayName: Install .NET SDK + inputs: + version: 9.0.x + + - task: Bash@3 + displayName: Install tools + inputs: + targetType: inline + script: | + set -euo pipefail + # Install roslyn-tools + FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" + echo "##vso[task.prependpath]$HOME/.dotnet/tools" + + # Install jq for JSON parsing + sudo apt-get update && sudo apt-get install -y jq + + - task: Bash@3 + displayName: Extract Roslyn version from AssetManifests + inputs: + targetType: inline + script: | + set -euo pipefail + + # The AssetManifests artifact is downloaded to this location + ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" + + # Find OfficialBuild.xml + XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" + + if [ ! -f "$XML_FILE" ]; then + echo "Error: OfficialBuild.xml not found at $XML_FILE" + ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" + exit 1 + fi + + # Extract version for Microsoft.CodeAnalysis package + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + + if [ -z "$VERSION" ]; then + # Try Microsoft.CodeAnalysis.Common + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + fi + + if [ -n "$VERSION" ]; then + echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" + echo "Latest Roslyn version from AssetManifest: $VERSION" + else + echo "Error: Could not extract version from AssetManifest" + exit 1 + fi + + # Also extract and verify commit from Build element + COMMIT=$(grep -oP ']*Commit="\K[^"]+' "$XML_FILE" | head -n1) + if [ -n "$COMMIT" ]; then + echo "Commit from AssetManifest: $COMMIT" + echo "Pipeline sourceCommit: $(RoslynEndSHA)" + fi + + - task: Bash@3 + displayName: Get current Roslyn SHA from package + inputs: + targetType: inline + script: | + set -euo pipefail + + # Read current version from package.json + CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) + + if [ -z "$CURRENT_VERSION" ]; then + echo "No roslyn version in package.json, this is first run" + echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" + exit 0 + fi + + echo "Current Roslyn version: $CURRENT_VERSION" + + # Download and extract commit SHA from NuGet package + TEMP_DIR=$(mktemp -d) + cd "$TEMP_DIR" + + PACKAGE_NAME="microsoft.codeanalysis.common" + DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" + PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" + + if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then + unzip -q package.nupkg + NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) + if [ -n "$NUSPEC_FILE" ]; then + START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") + if [ -n "$START_SHA" ]; then + echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" + echo "Current Roslyn SHA: $START_SHA" + fi + fi + fi + + cd - >/dev/null + rm -rf "$TEMP_DIR" + + - task: Bash@3 + displayName: Get latest Roslyn build and version + inputs: + targetType: inline + script: | + set -euo pipefail + + echo "Getting latest Roslyn version from NuGet feed..." + + # Get the latest version from the dotnet-tools feed + PACKAGE_NAME="microsoft.codeanalysis.common" + PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2/$PACKAGE_NAME/index.json" + + # Get all versions and pick the latest + VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") + + if [ -z "$VERSIONS" ]; then + # Try dotnet8 feed as fallback + PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/flat2/$PACKAGE_NAME/index.json" + VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") + fi + + # Get the latest prerelease version + VERSION=$(echo "$VERSIONS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-' | tail -n1) + + if [ -z "$VERSION" ]; then + echo "Error: Could not find any Roslyn versions in feed" + exit 1 + fi + + echo "Latest version from feed: $VERSION" + echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" + + # Download the package to get commit SHA + TEMP_DIR=$(mktemp -d) + cd "$TEMP_DIR" + + DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" + PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$VERSION/$PACKAGE_NAME.$VERSION.nupkg" + + if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then + unzip -q package.nupkg + NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) + if [ -n "$NUSPEC_FILE" ]; then + END_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") + if [ -n "$END_SHA" ]; then + echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" + echo "Latest Roslyn SHA: $END_SHA" + fi + fi + fi + + cd - >/dev/null + rm -rf "$TEMP_DIR" + + # If we couldn't get SHA, use GitHub as fallback + if [ -z "$END_SHA" ]; then + echo "Getting latest commit from GitHub..." + END_SHA=$(curl -s "https://api.github.com/repos/dotnet/roslyn/commits/main" | jq -r '.sha // empty') + echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" + fi + + - task: Bash@3 + displayName: Check if update needed + inputs: + targetType: inline + script: | + set -euo pipefail + + if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then + echo "No new commits to process" + echo "##vso[task.setvariable variable=SkipUpdate]true" + else + echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" + echo "##vso[task.setvariable variable=SkipUpdate]false" + fi + + - task: Bash@3 + displayName: Clone Roslyn repository + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn + + - task: Bash@3 + displayName: Setup auth for roslyn-tools + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + mkdir -p "$HOME/.roslyn-tools" + JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') + printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" + + - task: Bash@3 + displayName: Generate PR list + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + cd roslyn + + # Run pr-finder with VSCode label + OUTPUT=$(roslyn-tools pr-finder \ + -s "$(RoslynStartSHA)" \ + -e "$(RoslynEndSHA)" \ + --format changelog \ + --label VSCode 2>/dev/null || echo "") + + if [ -z "$OUTPUT" ]; then + echo "(no PRs with required labels)" > ../pr-changelog.txt + else + printf "%s\n" "$OUTPUT" > ../pr-changelog.txt + fi + + cd .. + cat pr-changelog.txt + + - task: Bash@3 + displayName: Update CHANGELOG and package.json + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Update package.json + jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp + mv package.json.tmp package.json + + # Update CHANGELOG.md + PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') + + # Read the current CHANGELOG + CHANGELOG_CONTENT=$(cat CHANGELOG.md) + + # Find and update the Roslyn bump line + # This is a simplified version - you may need to adjust based on your CHANGELOG format + echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' + /^\* Bump Roslyn to/ { + print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" + if (prs != " (no PRs with required labels)") { + print prs + } + next + } + /^ \*/ && prev ~ /^\* Bump Roslyn to/ { + next + } + { + prev = $0 + print + } + ' > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + - task: Bash@3 + displayName: Create and push branch + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Configure git + git config user.name "Azure Pipelines" + git config user.email "azuredevops@microsoft.com" + + # Create branch + BRANCH_NAME="roslyn-bump/$(RoslynEndSHA)" + git checkout -b "$BRANCH_NAME" + + # Commit changes + git add package.json CHANGELOG.md + git commit -m "Bump Roslyn to $(RoslynVersion)" + + # Push branch + git push origin "$BRANCH_NAME" + + echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" + + - task: Bash@3 + displayName: Create Pull Request + condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) + inputs: + targetType: inline + script: | + set -euo pipefail + + # Create PR using Azure DevOps REST API + PR_TITLE="Bump Roslyn to $(RoslynVersion)" + PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(resources.pipeline.officialBuildCI.runID))\n\nSee CHANGELOG.md for included PRs." + + # You would need to use Azure DevOps REST API or GitHub API here + # This is a placeholder for the actual PR creation + echo "Pull request would be created with:" + echo "Title: $PR_TITLE" + echo "Branch: $(PrBranch)" + echo "Target: ${{ parameters.targetBranch }}" + echo "Description: $PR_DESCRIPTION" From 0a8d36ea5ed282a62e46afa2990025f6e39f7a98 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Fri, 22 Aug 2025 14:07:40 +0530 Subject: [PATCH 02/12] using 1ES PT --- azure-pipelines/roslyn-version-bump.yml | 616 ++++++++++++------------ 1 file changed, 295 insertions(+), 321 deletions(-) diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml index 103e0ba46e..0a9726f04f 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/roslyn-version-bump.yml @@ -1,4 +1,5 @@ trigger: none +pr: none parameters: - name: createPullRequest @@ -11,6 +12,11 @@ parameters: default: main resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release pipelines: - pipeline: officialBuildCI source: 327 @@ -18,9 +24,6 @@ resources: branch: refs/heads/main trigger: none -pool: - vmImage: ubuntu-latest - variables: - name: RoslynStartSHA value: "" @@ -33,323 +36,294 @@ variables: - name: RoslynVersion value: "" -stages: - - stage: BumpRoslyn - displayName: Bump Roslyn Version - jobs: - - job: ProcessBump - displayName: Process Roslyn Bump - steps: - - checkout: self - persistCredentials: true - - - download: officialBuildCI - artifact: AssetManifests - displayName: Download AssetManifests from Roslyn build - - - task: UseDotNet@2 - displayName: Install .NET SDK - inputs: - version: 9.0.x - - - task: Bash@3 - displayName: Install tools - inputs: - targetType: inline - script: | - set -euo pipefail - # Install roslyn-tools - FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" - dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" - echo "##vso[task.prependpath]$HOME/.dotnet/tools" - - # Install jq for JSON parsing - sudo apt-get update && sudo apt-get install -y jq - - - task: Bash@3 - displayName: Extract Roslyn version from AssetManifests - inputs: - targetType: inline - script: | - set -euo pipefail - - # The AssetManifests artifact is downloaded to this location - ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" - - # Find OfficialBuild.xml - XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" - - if [ ! -f "$XML_FILE" ]; then - echo "Error: OfficialBuild.xml not found at $XML_FILE" - ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" - exit 1 - fi - - # Extract version for Microsoft.CodeAnalysis package - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - - if [ -z "$VERSION" ]; then - # Try Microsoft.CodeAnalysis.Common - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - fi - - if [ -n "$VERSION" ]; then - echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" - echo "Latest Roslyn version from AssetManifest: $VERSION" - else - echo "Error: Could not extract version from AssetManifest" - exit 1 - fi - - # Also extract and verify commit from Build element - COMMIT=$(grep -oP ']*Commit="\K[^"]+' "$XML_FILE" | head -n1) - if [ -n "$COMMIT" ]; then - echo "Commit from AssetManifest: $COMMIT" - echo "Pipeline sourceCommit: $(RoslynEndSHA)" - fi - - - task: Bash@3 - displayName: Get current Roslyn SHA from package - inputs: - targetType: inline - script: | - set -euo pipefail - - # Read current version from package.json - CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) - - if [ -z "$CURRENT_VERSION" ]; then - echo "No roslyn version in package.json, this is first run" - echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" - exit 0 - fi - - echo "Current Roslyn version: $CURRENT_VERSION" - - # Download and extract commit SHA from NuGet package - TEMP_DIR=$(mktemp -d) - cd "$TEMP_DIR" - - PACKAGE_NAME="microsoft.codeanalysis.common" - DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" - PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" - - if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then - unzip -q package.nupkg - NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) - if [ -n "$NUSPEC_FILE" ]; then - START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") - if [ -n "$START_SHA" ]; then - echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" - echo "Current Roslyn SHA: $START_SHA" +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: AzurePipelines-EO + image: 1ESPT-Ubuntu22.04 + os: linux + stages: + - stage: BumpRoslyn + displayName: Bump Roslyn Version + jobs: + - job: ProcessBump + displayName: Process Roslyn Bump + pool: + name: AzurePipelines-EO + image: 1ESPT-Ubuntu22.04 + os: linux + templateContext: + type: releaseJob + isProduction: false + inputs: + - input: pipelineArtifact + pipeline: officialBuildCI + artifactName: AssetManifests + destinationPath: $(Pipeline.Workspace)/officialBuildCI/AssetManifests + steps: + - checkout: self + persistCredentials: true + + - task: UseDotNet@2 + displayName: Install .NET SDK + inputs: + version: 9.0.x + + - task: Bash@3 + displayName: Install tools + inputs: + targetType: inline + script: | + set -euo pipefail + # Install roslyn-tools + FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" + echo "##vso[task.prependpath]$HOME/.dotnet/tools" + + # Install jq for JSON parsing + sudo apt-get update && sudo apt-get install -y jq + + - task: Bash@3 + displayName: Extract Roslyn version from AssetManifests + inputs: + targetType: inline + script: | + set -euo pipefail + + # The AssetManifests artifact is downloaded to this location + ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" + + # Find OfficialBuild.xml + XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" + + if [ ! -f "$XML_FILE" ]; then + echo "Error: OfficialBuild.xml not found at $XML_FILE" + ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" + exit 1 + fi + + # Extract version for Microsoft.CodeAnalysis package + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + + if [ -z "$VERSION" ]; then + # Try Microsoft.CodeAnalysis.Common + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + fi + + if [ -n "$VERSION" ]; then + echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" + echo "Latest Roslyn version from AssetManifest: $VERSION" + else + echo "Error: Could not extract version from AssetManifest" + exit 1 fi - fi - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - - - task: Bash@3 - displayName: Get latest Roslyn build and version - inputs: - targetType: inline - script: | - set -euo pipefail - - echo "Getting latest Roslyn version from NuGet feed..." - - # Get the latest version from the dotnet-tools feed - PACKAGE_NAME="microsoft.codeanalysis.common" - PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2/$PACKAGE_NAME/index.json" - - # Get all versions and pick the latest - VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") - - if [ -z "$VERSIONS" ]; then - # Try dotnet8 feed as fallback - PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/flat2/$PACKAGE_NAME/index.json" - VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") - fi - - # Get the latest prerelease version - VERSION=$(echo "$VERSIONS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-' | tail -n1) - - if [ -z "$VERSION" ]; then - echo "Error: Could not find any Roslyn versions in feed" - exit 1 - fi - - echo "Latest version from feed: $VERSION" - echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" - - # Download the package to get commit SHA - TEMP_DIR=$(mktemp -d) - cd "$TEMP_DIR" - - DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" - PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$VERSION/$PACKAGE_NAME.$VERSION.nupkg" - - if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then - unzip -q package.nupkg - NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) - if [ -n "$NUSPEC_FILE" ]; then - END_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") - if [ -n "$END_SHA" ]; then - echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" - echo "Latest Roslyn SHA: $END_SHA" + + # Display the END SHA from pipeline resource + echo "Using END SHA from pipeline resource: $(RoslynEndSHA)" + + - task: Bash@3 + displayName: Get current Roslyn SHA from package + inputs: + targetType: inline + script: | + set -euo pipefail + + # Read current version from package.json + CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) + + if [ -z "$CURRENT_VERSION" ]; then + echo "No roslyn version in package.json, this is first run" + echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" + exit 0 fi - fi - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - - # If we couldn't get SHA, use GitHub as fallback - if [ -z "$END_SHA" ]; then - echo "Getting latest commit from GitHub..." - END_SHA=$(curl -s "https://api.github.com/repos/dotnet/roslyn/commits/main" | jq -r '.sha // empty') - echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" - fi - - - task: Bash@3 - displayName: Check if update needed - inputs: - targetType: inline - script: | - set -euo pipefail - - if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then - echo "No new commits to process" - echo "##vso[task.setvariable variable=SkipUpdate]true" - else - echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" - echo "##vso[task.setvariable variable=SkipUpdate]false" - fi - - - task: Bash@3 - displayName: Clone Roslyn repository - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn - - - task: Bash@3 - displayName: Setup auth for roslyn-tools - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - mkdir -p "$HOME/.roslyn-tools" - JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') - printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" - - - task: Bash@3 - displayName: Generate PR list - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - cd roslyn - - # Run pr-finder with VSCode label - OUTPUT=$(roslyn-tools pr-finder \ - -s "$(RoslynStartSHA)" \ - -e "$(RoslynEndSHA)" \ - --format changelog \ - --label VSCode 2>/dev/null || echo "") - - if [ -z "$OUTPUT" ]; then - echo "(no PRs with required labels)" > ../pr-changelog.txt - else - printf "%s\n" "$OUTPUT" > ../pr-changelog.txt - fi - - cd .. - cat pr-changelog.txt - - - task: Bash@3 - displayName: Update CHANGELOG and package.json - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Update package.json - jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp - mv package.json.tmp package.json - - # Update CHANGELOG.md - PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') - - # Read the current CHANGELOG - CHANGELOG_CONTENT=$(cat CHANGELOG.md) - - # Find and update the Roslyn bump line - # This is a simplified version - you may need to adjust based on your CHANGELOG format - echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' - /^\* Bump Roslyn to/ { - print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" - if (prs != " (no PRs with required labels)") { - print prs - } - next - } - /^ \*/ && prev ~ /^\* Bump Roslyn to/ { - next - } - { - prev = $0 - print - } - ' > CHANGELOG.md.tmp - - mv CHANGELOG.md.tmp CHANGELOG.md - - - task: Bash@3 - displayName: Create and push branch - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Configure git - git config user.name "Azure Pipelines" - git config user.email "azuredevops@microsoft.com" - - # Create branch - BRANCH_NAME="roslyn-bump/$(RoslynEndSHA)" - git checkout -b "$BRANCH_NAME" - - # Commit changes - git add package.json CHANGELOG.md - git commit -m "Bump Roslyn to $(RoslynVersion)" - - # Push branch - git push origin "$BRANCH_NAME" - - echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" - - - task: Bash@3 - displayName: Create Pull Request - condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) - inputs: - targetType: inline - script: | - set -euo pipefail - - # Create PR using Azure DevOps REST API - PR_TITLE="Bump Roslyn to $(RoslynVersion)" - PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(resources.pipeline.officialBuildCI.runID))\n\nSee CHANGELOG.md for included PRs." - - # You would need to use Azure DevOps REST API or GitHub API here - # This is a placeholder for the actual PR creation - echo "Pull request would be created with:" - echo "Title: $PR_TITLE" - echo "Branch: $(PrBranch)" - echo "Target: ${{ parameters.targetBranch }}" - echo "Description: $PR_DESCRIPTION" + + echo "Current Roslyn version: $CURRENT_VERSION" + + # Download and extract commit SHA from NuGet package + TEMP_DIR=$(mktemp -d) + cd "$TEMP_DIR" + + PACKAGE_NAME="microsoft.codeanalysis.common" + DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" + PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" + + if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then + unzip -q package.nupkg + NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) + if [ -n "$NUSPEC_FILE" ]; then + START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") + if [ -n "$START_SHA" ]; then + echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" + echo "Current Roslyn SHA: $START_SHA" + fi + fi + fi + + cd - >/dev/null + rm -rf "$TEMP_DIR" + + - task: Bash@3 + displayName: Check if update needed + inputs: + targetType: inline + script: | + set -euo pipefail + + echo "START SHA: $(RoslynStartSHA)" + echo "END SHA: $(RoslynEndSHA)" + echo "New Roslyn Version: $(RoslynVersion)" + + if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then + echo "No new commits to process" + echo "##vso[task.setvariable variable=SkipUpdate]true" + else + echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" + echo "##vso[task.setvariable variable=SkipUpdate]false" + fi + + - task: Bash@3 + displayName: Clone Roslyn repository + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn + + - task: Bash@3 + displayName: Setup auth for roslyn-tools + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + mkdir -p "$HOME/.roslyn-tools" + JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') + printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" + + - task: Bash@3 + displayName: Generate PR list + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + cd roslyn + + # Run pr-finder with VSCode label + OUTPUT=$(roslyn-tools pr-finder \ + -s "$(RoslynStartSHA)" \ + -e "$(RoslynEndSHA)" \ + --format changelog \ + --label VSCode 2>/dev/null || echo "") + + if [ -z "$OUTPUT" ]; then + echo "(no PRs with required labels)" > ../pr-changelog.txt + else + printf "%s\n" "$OUTPUT" > ../pr-changelog.txt + fi + + cd .. + cat pr-changelog.txt + + - task: Bash@3 + displayName: Update CHANGELOG and package.json + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Update package.json with new Roslyn version from AssetManifest + jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp + mv package.json.tmp package.json + + # Update CHANGELOG.md + PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') + + # Read the current CHANGELOG + CHANGELOG_CONTENT=$(cat CHANGELOG.md) + + # Find and update the Roslyn bump line + echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' + /^\* Bump Roslyn to/ { + print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" + if (prs != " (no PRs with required labels)") { + print prs + } + next + } + /^ \*/ && prev ~ /^\* Bump Roslyn to/ { + next + } + { + prev = $0 + print + } + ' > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + - task: Bash@3 + displayName: Create and push branch + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Configure git + git config user.name "Azure Pipelines" + git config user.email "azuredevops@microsoft.com" + + # Create branch using the first 8 chars of END SHA for shorter branch name + SHORT_SHA=$(echo "$(RoslynEndSHA)" | cut -c1-8) + BRANCH_NAME="roslyn-bump/$SHORT_SHA" + git checkout -b "$BRANCH_NAME" + + # Commit changes + git add package.json CHANGELOG.md + git commit -m "Bump Roslyn to $(RoslynVersion)" + + # Push branch + git push origin "$BRANCH_NAME" + + echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" + + - task: Bash@3 + displayName: Create Pull Request + condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) + inputs: + targetType: inline + script: | + set -euo pipefail + + # Create PR using Azure DevOps REST API + PR_TITLE="Bump Roslyn to $(RoslynVersion)" + PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(RoslynBuildId))\n\nSee CHANGELOG.md for included PRs." + + # You would need to use Azure DevOps REST API or GitHub API here + # This is a placeholder for the actual PR creation + echo "Pull request would be created with:" + echo "Title: $PR_TITLE" + echo "Branch: $(PrBranch)" + echo "Target: ${{ parameters.targetBranch }}" + echo "Description: $PR_DESCRIPTION" + /^\* Bump Roslyn to/ { + print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" + if (prs != " (no PRs with required labels)") { + print prs + } + next + } + /^ \*/ && prev ~ /^\* Bump Roslyn to/ { + next + } + { + prev = $0 + print + } + ' > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + From 1e19e616f4dae734951e92b588820e4fc1dded1e Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Mon, 25 Aug 2025 20:58:36 +0530 Subject: [PATCH 03/12] Using gulp taks --- azure-pipelines/roslyn-version-bump.yml | 300 +++-------------------- tasks/insertionTasks.ts | 308 ++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 268 deletions(-) create mode 100644 tasks/insertionTasks.ts diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml index 0a9726f04f..3856bc53e0 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/roslyn-version-bump.yml @@ -19,29 +19,25 @@ resources: ref: refs/tags/release pipelines: - pipeline: officialBuildCI - source: 327 + source: dotnet-roslyn-official project: internal - branch: refs/heads/main + branch: main trigger: none variables: - - name: RoslynStartSHA - value: "" - name: RoslynEndSHA value: $(resources.pipeline.officialBuildCI.sourceCommit) - name: RoslynBuildNumber value: $(resources.pipeline.officialBuildCI.runName) - name: RoslynBuildId value: $(resources.pipeline.officialBuildCI.runID) - - name: RoslynVersion - value: "" extends: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: name: AzurePipelines-EO - image: 1ESPT-Ubuntu22.04 + image: AzurePipelinesUbuntu22.04compliant os: linux stages: - stage: BumpRoslyn @@ -51,16 +47,8 @@ extends: displayName: Process Roslyn Bump pool: name: AzurePipelines-EO - image: 1ESPT-Ubuntu22.04 + image: AzurePipelinesUbuntu22.04compliant os: linux - templateContext: - type: releaseJob - isProduction: false - inputs: - - input: pipelineArtifact - pipeline: officialBuildCI - artifactName: AssetManifests - destinationPath: $(Pipeline.Workspace)/officialBuildCI/AssetManifests steps: - checkout: self persistCredentials: true @@ -70,260 +58,36 @@ extends: inputs: version: 9.0.x - - task: Bash@3 - displayName: Install tools + - task: NodeTool@0 + displayName: Install Node.js inputs: - targetType: inline - script: | - set -euo pipefail - # Install roslyn-tools - FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" - dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" - echo "##vso[task.prependpath]$HOME/.dotnet/tools" + versionSpec: '20.x' - # Install jq for JSON parsing - sudo apt-get update && sudo apt-get install -y jq - - - task: Bash@3 - displayName: Extract Roslyn version from AssetManifests - inputs: - targetType: inline - script: | - set -euo pipefail - - # The AssetManifests artifact is downloaded to this location - ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" - - # Find OfficialBuild.xml - XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" - - if [ ! -f "$XML_FILE" ]; then - echo "Error: OfficialBuild.xml not found at $XML_FILE" - ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" - exit 1 - fi - - # Extract version for Microsoft.CodeAnalysis package - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - - if [ -z "$VERSION" ]; then - # Try Microsoft.CodeAnalysis.Common - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - fi - - if [ -n "$VERSION" ]; then - echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" - echo "Latest Roslyn version from AssetManifest: $VERSION" - else - echo "Error: Could not extract version from AssetManifest" - exit 1 - fi - - # Display the END SHA from pipeline resource - echo "Using END SHA from pipeline resource: $(RoslynEndSHA)" - - - task: Bash@3 - displayName: Get current Roslyn SHA from package - inputs: - targetType: inline - script: | - set -euo pipefail - - # Read current version from package.json - CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) - - if [ -z "$CURRENT_VERSION" ]; then - echo "No roslyn version in package.json, this is first run" - echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" - exit 0 - fi - - echo "Current Roslyn version: $CURRENT_VERSION" - - # Download and extract commit SHA from NuGet package - TEMP_DIR=$(mktemp -d) - cd "$TEMP_DIR" - - PACKAGE_NAME="microsoft.codeanalysis.common" - DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" - PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" - - if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then - unzip -q package.nupkg - NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) - if [ -n "$NUSPEC_FILE" ]; then - START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") - if [ -n "$START_SHA" ]; then - echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" - echo "Current Roslyn SHA: $START_SHA" - fi - fi - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - - - task: Bash@3 - displayName: Check if update needed - inputs: - targetType: inline - script: | - set -euo pipefail - - echo "START SHA: $(RoslynStartSHA)" - echo "END SHA: $(RoslynEndSHA)" - echo "New Roslyn Version: $(RoslynVersion)" - - if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then - echo "No new commits to process" - echo "##vso[task.setvariable variable=SkipUpdate]true" - else - echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" - echo "##vso[task.setvariable variable=SkipUpdate]false" - fi - - - task: Bash@3 - displayName: Clone Roslyn repository - condition: ne(variables['SkipUpdate'], 'true') + - task: DownloadPipelineArtifact@2 + displayName: Download Asset Manifests inputs: - targetType: inline - script: | - git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn - - - task: Bash@3 - displayName: Setup auth for roslyn-tools - condition: ne(variables['SkipUpdate'], 'true') + source: specific + project: internal + pipeline: dotnet-roslyn-official + runVersion: specific + runId: $(RoslynBuildId) + artifact: AssetManifests + path: $(Pipeline.Workspace)/AssetManifests + + - pwsh: | + npm ci + npm install + npm install -g gulp + gulp installDependencies + displayName: 'Install npm dependencies and gulp' + + - task: Npm@1 + displayName: Run Roslyn insertion + env: + GITHUB_TOKEN: $(System.AccessToken) + GitHubPAT: $(System.AccessToken) inputs: - targetType: inline - script: | - set -euo pipefail - mkdir -p "$HOME/.roslyn-tools" - JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') - printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" - - - task: Bash@3 - displayName: Generate PR list - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - cd roslyn - - # Run pr-finder with VSCode label - OUTPUT=$(roslyn-tools pr-finder \ - -s "$(RoslynStartSHA)" \ - -e "$(RoslynEndSHA)" \ - --format changelog \ - --label VSCode 2>/dev/null || echo "") - - if [ -z "$OUTPUT" ]; then - echo "(no PRs with required labels)" > ../pr-changelog.txt - else - printf "%s\n" "$OUTPUT" > ../pr-changelog.txt - fi - - cd .. - cat pr-changelog.txt - - - task: Bash@3 - displayName: Update CHANGELOG and package.json - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Update package.json with new Roslyn version from AssetManifest - jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp - mv package.json.tmp package.json - - # Update CHANGELOG.md - PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') - - # Read the current CHANGELOG - CHANGELOG_CONTENT=$(cat CHANGELOG.md) - - # Find and update the Roslyn bump line - echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' - /^\* Bump Roslyn to/ { - print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" - if (prs != " (no PRs with required labels)") { - print prs - } - next - } - /^ \*/ && prev ~ /^\* Bump Roslyn to/ { - next - } - { - prev = $0 - print - } - ' > CHANGELOG.md.tmp - - mv CHANGELOG.md.tmp CHANGELOG.md - - - task: Bash@3 - displayName: Create and push branch - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Configure git - git config user.name "Azure Pipelines" - git config user.email "azuredevops@microsoft.com" - - # Create branch using the first 8 chars of END SHA for shorter branch name - SHORT_SHA=$(echo "$(RoslynEndSHA)" | cut -c1-8) - BRANCH_NAME="roslyn-bump/$SHORT_SHA" - git checkout -b "$BRANCH_NAME" - - # Commit changes - git add package.json CHANGELOG.md - git commit -m "Bump Roslyn to $(RoslynVersion)" - - # Push branch - git push origin "$BRANCH_NAME" - - echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" - - - task: Bash@3 - displayName: Create Pull Request - condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) - inputs: - targetType: inline - script: | - set -euo pipefail - - # Create PR using Azure DevOps REST API - PR_TITLE="Bump Roslyn to $(RoslynVersion)" - PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(RoslynBuildId))\n\nSee CHANGELOG.md for included PRs." - - # You would need to use Azure DevOps REST API or GitHub API here - # This is a placeholder for the actual PR creation - echo "Pull request would be created with:" - echo "Title: $PR_TITLE" - echo "Branch: $(PrBranch)" - echo "Target: ${{ parameters.targetBranch }}" - echo "Description: $PR_DESCRIPTION" - /^\* Bump Roslyn to/ { - print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" - if (prs != " (no PRs with required labels)") { - print prs - } - next - } - /^ \*/ && prev ~ /^\* Bump Roslyn to/ { - next - } - { - prev = $0 - print - } - ' > CHANGELOG.md.tmp + command: custom + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' - mv CHANGELOG.md.tmp CHANGELOG.md diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts new file mode 100644 index 0000000000..7a8c02e132 --- /dev/null +++ b/tasks/insertionTasks.ts @@ -0,0 +1,308 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as gulp from 'gulp'; +import * as fs from 'fs'; +import * as path from 'path'; +import minimist from 'minimist'; +import { Octokit } from '@octokit/rest'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +import fetch from 'node-fetch'; +import * as xml2js from 'xml2js'; +import * as AdmZip from 'adm-zip'; + +const execAsync = promisify(exec); + +interface InsertionOptions { + roslynVersion?: string; + roslynStartSHA?: string; + roslynEndSHA?: string; + roslynBuildId?: string; + roslynBuildNumber?: string; + assetManifestPath?: string; + createPullRequest?: boolean; + targetBranch?: string; + githubPAT?: string; + dryRun?: boolean; +} + +gulp.task('insertion:roslyn', async (): Promise => { + const options = minimist(process.argv.slice(2)); + + console.log('Starting Roslyn insertion process...'); + console.log(`Options: ${JSON.stringify(options, null, 2)}`); + + try { + // Step 1: Extract Roslyn version from AssetManifest + if (!options.assetManifestPath) { + throw new Error('assetManifestPath is required'); + } + + const newVersion = await extractRoslynVersionFromManifest(options.assetManifestPath); + if (!newVersion) { + throw new Error('Failed to extract Roslyn version from asset manifest'); + } + options.roslynVersion = newVersion; + console.log(`New Roslyn version: ${newVersion}`); + + // Step 2: Get current SHA from package + const currentSHA = await getCurrentRoslynSHA(); + options.roslynStartSHA = currentSHA || '0000000000000000000000000000000000000000'; + console.log(`Current Roslyn SHA: ${options.roslynStartSHA}`); + + // Step 3: Check if update needed + if (!options.roslynEndSHA) { + throw new Error('roslynEndSHA is required'); + } + + if (options.roslynStartSHA === options.roslynEndSHA) { + console.log('No new commits to process - versions are identical'); + return; + } + + console.log(`Update needed: ${options.roslynStartSHA}..${options.roslynEndSHA}`); + + // Step 4: Clone Roslyn repo and generate PR list + await cloneRoslynRepo(); + const prList = await generatePRList(options.roslynStartSHA, options.roslynEndSHA); + console.log('PR List generated:', prList); + + // Step 5: Update files + await updatePackageJson(options.roslynVersion); + await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); + + // Step 6: Create branch and PR if not dry run + if (!options.dryRun) { + const branchName = await createBranch(options.roslynEndSHA, options.roslynVersion); + console.log(`Branch created: ${branchName}`); + + if (options.createPullRequest) { + await createPullRequest(branchName, options); + console.log('Pull request created successfully'); + } + } else { + console.log('Dry run mode - skipping branch and PR creation'); + } + + } catch (error) { + console.error('Insertion failed:', error); + throw error; + } finally { + // Cleanup + if (fs.existsSync('roslyn')) { + await execAsync('rm -rf roslyn'); + } + } +}); + +async function extractRoslynVersionFromManifest(manifestPath: string): Promise { + const xmlFile = path.join(manifestPath, 'OfficialBuild.xml'); + + if (!fs.existsSync(xmlFile)) { + console.error(`OfficialBuild.xml not found at ${xmlFile}`); + return null; + } + + const xmlContent = fs.readFileSync(xmlFile, 'utf8'); + const parser = new xml2js.Parser(); + const result = await parser.parseStringPromise(xmlContent); + + // Navigate the XML structure to find Microsoft.CodeAnalysis package + const packages = result?.Build?.Package || []; + for (const pkg of packages) { + const attrs = pkg.$; + if (attrs?.Id === 'Microsoft.CodeAnalysis' || attrs?.Id === 'Microsoft.CodeAnalysis.Common') { + return attrs.Version; + } + } + + return null; +} + +async function getCurrentRoslynSHA(): Promise { + const packageJsonContent = fs.readFileSync('package.json', 'utf8'); + const packageJson = JSON.parse(packageJsonContent); + const currentVersion = packageJson.defaults?.roslyn; + + if (!currentVersion) { + console.log('No roslyn version in package.json, this is first run'); + return null; + } + + console.log(`Current Roslyn version: ${currentVersion}`); + + // Download and extract commit SHA from NuGet package + const packageName = 'microsoft.codeanalysis.common'; + const feed = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2'; + const packageUrl = `${feed}/${packageName}/${currentVersion}/${packageName}.${currentVersion}.nupkg`; + + try { + const response = await fetch(packageUrl); + if (!response.ok) { + console.error(`Failed to download package: ${response.statusText}`); + return null; + } + + const buffer = await response.buffer(); + const tempFile = path.join(process.cwd(), 'temp-package.nupkg'); + fs.writeFileSync(tempFile, buffer); + + const zip = new AdmZip(tempFile); + const entries = zip.getEntries(); + + for (const entry of entries) { + if (entry.entryName.endsWith('.nuspec')) { + const nuspecContent = zip.readAsText(entry); + const shaMatch = nuspecContent.match(/repository[^>]*commit="([a-f0-9]{40})"/); + fs.unlinkSync(tempFile); + return shaMatch ? shaMatch[1] : null; + } + } + + fs.unlinkSync(tempFile); + } catch (error) { + console.error('Error getting current SHA:', error); + } + + return null; +} + +async function cloneRoslynRepo(): Promise { + console.log('Cloning Roslyn repository...'); + await execAsync('git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn'); +} + +async function generatePRList(startSHA: string, endSHA: string): Promise { + console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); + + // Setup auth for roslyn-tools + const homeDir = process.env.HOME || process.env.USERPROFILE; + const settingsDir = path.join(homeDir!, '.roslyn-tools'); + if (!fs.existsSync(settingsDir)) { + fs.mkdirSync(settingsDir, { recursive: true }); + } + + const authJson = { + GitHubToken: process.env.GITHUB_TOKEN || '', + DevDivAzureDevOpsToken: '', + DncEngAzureDevOpsToken: '' + }; + const settingsFile = path.join(settingsDir, 'settings'); + fs.writeFileSync(settingsFile, Buffer.from(JSON.stringify(authJson)).toString('base64')); + + try { + const { stdout } = await execAsync( + `cd roslyn && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, + { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer + ); + return stdout || '(no PRs with required labels)'; + } catch (error) { + console.warn('PR finder failed, using empty list:', error); + return '(no PRs with required labels)'; + } +} + +async function updatePackageJson(newVersion: string): Promise { + console.log(`Updating package.json with Roslyn version ${newVersion}...`); + const packageJsonPath = 'package.json'; + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + if (!packageJson.defaults) { + packageJson.defaults = {}; + } + packageJson.defaults.roslyn = newVersion; + + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); +} + +async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { + console.log('Updating CHANGELOG.md...'); + const changelogPath = 'CHANGELOG.md'; + let changelogContent = fs.readFileSync(changelogPath, 'utf8'); + + // Format PR list with proper indentation + const formattedPRList = prList.split('\n').map(line => ` ${line}`).join('\n'); + + // Find and update the Roslyn bump line + const lines = changelogContent.split('\n'); + const newLines: string[] = []; + let skipNext = false; + + for (let i = 0; i < lines.length; i++) { + if (skipNext && lines[i].startsWith(' *')) { + continue; + } + skipNext = false; + + if (lines[i].match(/^\* Bump Roslyn to/)) { + const prLink = buildNumber && buildId + ? `[${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + newLines.push(`* Bump Roslyn to ${version} (PR: ${prLink})`); + if (prList !== '(no PRs with required labels)') { + newLines.push(formattedPRList); + } + skipNext = true; + } else { + newLines.push(lines[i]); + } + } + + fs.writeFileSync(changelogPath, newLines.join('\n')); +} + +async function createBranch(endSHA: string, version: string): Promise { + console.log('Creating and pushing branch...'); + + await execAsync('git config user.name "Azure Pipelines"'); + await execAsync('git config user.email "azuredevops@microsoft.com"'); + + const shortSHA = endSHA.substring(0, 8); + const branchName = `roslyn-bump/${shortSHA}`; + + await execAsync(`git checkout -b ${branchName}`); + await execAsync('git add package.json CHANGELOG.md'); + await execAsync(`git commit -m "Bump Roslyn to ${version}"`); + await execAsync(`git push origin ${branchName}`); + + return branchName; +} + +async function createPullRequest(branchName: string, options: InsertionOptions): Promise { + console.log('Creating pull request...'); + + if (!options.githubPAT) { + console.warn('No GitHub PAT provided, skipping PR creation'); + return; + } + + const octokit = new Octokit({ auth: options.githubPAT }); + + const prTitle = `Bump Roslyn to ${options.roslynVersion}`; + const prBody = `Automated Roslyn version bump. + +**Version:** \`${options.roslynVersion}\` +**Commit Range:** \`${options.roslynStartSHA}...${options.roslynEndSHA}\` +**Azure DevOps Build:** [${options.roslynBuildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${options.roslynBuildId}) + +See CHANGELOG.md for included PRs.`; + + try { + const { data } = await octokit.pulls.create({ + owner: 'dotnet', + repo: 'vscode-csharp', + title: prTitle, + body: prBody, + head: branchName, + base: options.targetBranch || 'main' + }); + + console.log(`Pull request created: ${data.html_url}`); + } catch (error) { + console.error('Failed to create PR:', error); + throw error; + } +} From 225a38fa95d1f83c3f11e1565a98a1f6802377b2 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Tue, 26 Aug 2025 23:40:07 +0530 Subject: [PATCH 04/12] resolved review comments --- azure-pipelines/roslyn-version-bump.yml | 7 +- tasks/insertionTasks.ts | 287 +++++++++++++++--------- 2 files changed, 181 insertions(+), 113 deletions(-) diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml index 3856bc53e0..e5526773f2 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/roslyn-version-bump.yml @@ -81,6 +81,11 @@ extends: gulp installDependencies displayName: 'Install npm dependencies and gulp' + - script: | + echo "Cloning Roslyn repository..." + git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git $(Pipeline.Workspace)/roslyn + displayName: 'Clone Roslyn repository' + - task: Npm@1 displayName: Run Roslyn insertion env: @@ -88,6 +93,6 @@ extends: GitHubPAT: $(System.AccessToken) inputs: command: custom - customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 7a8c02e132..7de5ba59ba 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -6,24 +6,32 @@ import * as gulp from 'gulp'; import * as fs from 'fs'; import * as path from 'path'; +import * as os from 'os'; +import minimist from 'minimist'; + +function logWarning(message: string): void { + console.log(`##vso[task.logissue type=warning]${message}`); +} + +function logError(message: string): void { + console.log(`##vso[task.logissue type=error]${message}`); +} + import minimist from 'minimist'; -import { Octokit } from '@octokit/rest'; import { exec } from 'child_process'; import { promisify } from 'util'; import fetch from 'node-fetch'; -import * as xml2js from 'xml2js'; import * as AdmZip from 'adm-zip'; const execAsync = promisify(exec); interface InsertionOptions { roslynVersion?: string; - roslynStartSHA?: string; roslynEndSHA?: string; roslynBuildId?: string; roslynBuildNumber?: string; assetManifestPath?: string; - createPullRequest?: boolean; + roslynRepoPath?: string; targetBranch?: string; githubPAT?: string; dryRun?: boolean; @@ -50,51 +58,47 @@ gulp.task('insertion:roslyn', async (): Promise => { // Step 2: Get current SHA from package const currentSHA = await getCurrentRoslynSHA(); - options.roslynStartSHA = currentSHA || '0000000000000000000000000000000000000000'; - console.log(`Current Roslyn SHA: ${options.roslynStartSHA}`); + if (!currentSHA) { + throw new Error('Could not determine current Roslyn SHA from package'); + } + console.log(`Current Roslyn SHA: ${currentSHA}`); // Step 3: Check if update needed if (!options.roslynEndSHA) { throw new Error('roslynEndSHA is required'); } - if (options.roslynStartSHA === options.roslynEndSHA) { + if (currentSHA === options.roslynEndSHA) { console.log('No new commits to process - versions are identical'); return; } - console.log(`Update needed: ${options.roslynStartSHA}..${options.roslynEndSHA}`); + console.log(`Update needed: ${currentSHA}..${options.roslynEndSHA}`); - // Step 4: Clone Roslyn repo and generate PR list - await cloneRoslynRepo(); - const prList = await generatePRList(options.roslynStartSHA, options.roslynEndSHA); + // Step 4: Verify Roslyn repo exists + if (!options.roslynRepoPath) { + throw new Error('roslynRepoPath is required'); + } + await verifyRoslynRepo(options.roslynRepoPath); + + // Step 5: Generate PR list + const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); - // Step 5: Update files + // Step 6: Update files await updatePackageJson(options.roslynVersion); await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); - // Step 6: Create branch and PR if not dry run - if (!options.dryRun) { - const branchName = await createBranch(options.roslynEndSHA, options.roslynVersion); - console.log(`Branch created: ${branchName}`); - - if (options.createPullRequest) { - await createPullRequest(branchName, options); - console.log('Pull request created successfully'); - } - } else { - console.log('Dry run mode - skipping branch and PR creation'); - } + // Step 6: Create branch and PR + await createBranchAndPR(options.roslynVersion, options.roslynEndSHA, options); } catch (error) { - console.error('Insertion failed:', error); - throw error; - } finally { - // Cleanup - if (fs.existsSync('roslyn')) { - await execAsync('rm -rf roslyn'); + const errorMessage = error instanceof Error ? error.message : String(error); + logError(`Insertion failed: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); } + throw error; } }); @@ -102,7 +106,7 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { console.log(`Current Roslyn version: ${currentVersion}`); - // Download and extract commit SHA from NuGet package - const packageName = 'microsoft.codeanalysis.common'; - const feed = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2'; - const packageUrl = `${feed}/${packageName}/${currentVersion}/${packageName}.${currentVersion}.nupkg`; - try { - const response = await fetch(packageUrl); - if (!response.ok) { - console.error(`Failed to download package: ${response.statusText}`); + const packageName = 'microsoft.codeanalysis.common'; + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), currentVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); + + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); return null; } - - const buffer = await response.buffer(); - const tempFile = path.join(process.cwd(), 'temp-package.nupkg'); - fs.writeFileSync(tempFile, buffer); - - const zip = new AdmZip(tempFile); - const entries = zip.getEntries(); - - for (const entry of entries) { - if (entry.entryName.endsWith('.nuspec')) { - const nuspecContent = zip.readAsText(entry); - const shaMatch = nuspecContent.match(/repository[^>]*commit="([a-f0-9]{40})"/); - fs.unlinkSync(tempFile); - return shaMatch ? shaMatch[1] : null; - } + + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*?)"/.exec(nuspecFile); + if (results == null || results.length === 0) { + logError('Failed to find commit number from nuspec file'); + return null; } + + if (results.length !== 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`Found commit SHA: ${commitNumber}`); + return commitNumber; - fs.unlinkSync(tempFile); } catch (error) { - console.error('Error getting current SHA:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logError(`Error getting current SHA: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } + return null; } - - return null; } -async function cloneRoslynRepo(): Promise { - console.log('Cloning Roslyn repository...'); - await execAsync('git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn'); +async function verifyRoslynRepo(roslynRepoPath: string): Promise { + if (!fs.existsSync(roslynRepoPath)) { + throw new Error(`Roslyn repository not found at ${roslynRepoPath}`); + } + console.log(`Using Roslyn repository at ${roslynRepoPath}`); } -async function generatePRList(startSHA: string, endSHA: string): Promise { +async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); // Setup auth for roslyn-tools @@ -186,7 +199,7 @@ async function generatePRList(startSHA: string, endSHA: string): Promise } const authJson = { - GitHubToken: process.env.GITHUB_TOKEN || '', + GitHubToken: options.githubPAT || '', DevDivAzureDevOpsToken: '', DncEngAzureDevOpsToken: '' }; @@ -195,12 +208,16 @@ async function generatePRList(startSHA: string, endSHA: string): Promise try { const { stdout } = await execAsync( - `cd roslyn && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, + `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); return stdout || '(no PRs with required labels)'; } catch (error) { - console.warn('PR finder failed, using empty list:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logWarning(`PR finder failed, using empty list: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } return '(no PRs with required labels)'; } } @@ -209,9 +226,9 @@ async function updatePackageJson(newVersion: string): Promise { console.log(`Updating package.json with Roslyn version ${newVersion}...`); const packageJsonPath = 'package.json'; const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - + if (!packageJson.defaults) { - packageJson.defaults = {}; + throw new Error('Could not find defaults section in package.json'); } packageJson.defaults.roslyn = newVersion; @@ -224,10 +241,10 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st let changelogContent = fs.readFileSync(changelogPath, 'utf8'); // Format PR list with proper indentation - const formattedPRList = prList.split('\n').map(line => ` ${line}`).join('\n'); + const formattedPRList = prList.split('\n').map(line => ` ${line}`).join(os.EOL); // Find and update the Roslyn bump line - const lines = changelogContent.split('\n'); + const lines = changelogContent.split(/\r?\n/); const newLines: string[] = []; let skipNext = false; @@ -254,55 +271,101 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st fs.writeFileSync(changelogPath, newLines.join('\n')); } -async function createBranch(endSHA: string, version: string): Promise { - console.log('Creating and pushing branch...'); - - await execAsync('git config user.name "Azure Pipelines"'); - await execAsync('git config user.email "azuredevops@microsoft.com"'); - - const shortSHA = endSHA.substring(0, 8); - const branchName = `roslyn-bump/${shortSHA}`; - - await execAsync(`git checkout -b ${branchName}`); - await execAsync('git add package.json CHANGELOG.md'); - await execAsync(`git commit -m "Bump Roslyn to ${version}"`); - await execAsync(`git push origin ${branchName}`); - - return branchName; -} - -async function createPullRequest(branchName: string, options: InsertionOptions): Promise { - console.log('Creating pull request...'); - - if (!options.githubPAT) { - console.warn('No GitHub PAT provided, skipping PR creation'); - return; - } - - const octokit = new Octokit({ auth: options.githubPAT }); - - const prTitle = `Bump Roslyn to ${options.roslynVersion}`; - const prBody = `Automated Roslyn version bump. - -**Version:** \`${options.roslynVersion}\` -**Commit Range:** \`${options.roslynStartSHA}...${options.roslynEndSHA}\` -**Azure DevOps Build:** [${options.roslynBuildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${options.roslynBuildId}) - -See CHANGELOG.md for included PRs.`; - +async function createBranchAndPR(version: string, endSHA: string, options: InsertionOptions): Promise { try { - const { data } = await octokit.pulls.create({ + console.log('Creating and pushing branch...'); + + // Configure git user + await git(['config', '--local', 'user.name', 'Azure Pipelines']); + await git(['config', '--local', 'user.email', 'azuredevops@microsoft.com']); + + // Check for changes + const changedFiles = await git(['diff', '--name-only', 'HEAD']); + if (!changedFiles) { + console.log('No files changed, skipping branch creation'); + return; + } + + console.log(`Changed files: ${changedFiles}`); + + // Create and checkout new branch + const shortSHA = endSHA.substring(0, 8); + const branchName = `insertion/${shortSHA}`; + await git(['checkout', '-b', branchName]); + + // Stage and commit changes + await git(['add', 'package.json', 'CHANGELOG.md']); + await git(['commit', '-m', `Bump Roslyn to ${version} (${shortSHA})`]); + + // Configure remote with PAT for authentication + const pat = options.githubPAT; + if (!pat) { + throw new Error('GitHub PAT is required to push changes'); + } + + const remoteRepoAlias = 'targetRepo'; + await git( + [ + 'remote', + 'add', + remoteRepoAlias, + `https://${pat}@github.com/dotnet/vscode-csharp.git`, + ], + false // Don't log the command to avoid exposing the PAT + ); + + // Check if branch already exists + const lsRemote = await git(['ls-remote', '--heads', remoteRepoAlias, branchName]); + if (lsRemote.trim() !== '') { + console.log(`##vso[task.logissue type=warning]${branchName} already exists. Skipping push.`); + return; + } + + // Push the branch + await git(['push', '-u', remoteRepoAlias, branchName]); + + // Create PR if not in dry run mode + if (options.dryRun) { + console.log('Dry run: Would create PR for branch', branchName); + return; + } + + console.log('Creating pull request...'); + const octokit = new Octokit({ auth: pat }); + const title = `Bump Roslyn to ${version} (${shortSHA})`; + + // Check if PR already exists + const listPullRequest = await octokit.rest.pulls.list({ owner: 'dotnet', repo: 'vscode-csharp', - title: prTitle, - body: prBody, + head: `dotnet:${branchName}`, + state: 'open' + }); + + if (listPullRequest.data.length > 0) { + console.log(`Pull request already exists: ${listPullRequest.data[0].html_url}`); + return; + } + + // Create the PR + const pr = await octokit.rest.pulls.create({ + owner: 'dotnet', + repo: 'vscode-csharp', + title: title, head: branchName, - base: options.targetBranch || 'main' + base: options.targetBranch || 'main', + body: `This is an automated PR to update the Roslyn version to ${version} (${shortSHA})`, + maintainer_can_modify: true }); - console.log(`Pull request created: ${data.html_url}`); + console.log(`Created pull request: ${pr.data.html_url}`); + } catch (error) { - console.error('Failed to create PR:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logError(`Failed to create branch/PR: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } throw error; } } From e4cf965903c85a6810b7d64218abb5caae0219de Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Wed, 27 Aug 2025 00:19:38 +0530 Subject: [PATCH 05/12] renamed pipline --- ...-version-bump.yml => dotnet-vscode-csharp-insertion.yml} | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename azure-pipelines/{roslyn-version-bump.yml => dotnet-vscode-csharp-insertion.yml} (91%) diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml similarity index 91% rename from azure-pipelines/roslyn-version-bump.yml rename to azure-pipelines/dotnet-vscode-csharp-insertion.yml index e5526773f2..a26969a3e0 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -2,10 +2,6 @@ trigger: none pr: none parameters: - - name: createPullRequest - displayName: Create Pull Request - type: boolean - default: true - name: targetBranch displayName: Target Branch for PR type: string @@ -93,6 +89,6 @@ extends: GitHubPAT: $(System.AccessToken) inputs: command: custom - customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' From cc3f2df401656a80a34319a7ef28841636c211e4 Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Wed, 27 Aug 2025 03:51:23 +0530 Subject: [PATCH 06/12] added insertion task in gulp --- gulpfile.ts | 1 + tasks/insertionTasks.ts | 118 +++++++++++++++++++++++----------------- 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/gulpfile.ts b/gulpfile.ts index ef52553ced..e4f8732493 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -12,3 +12,4 @@ require('./tasks/debuggerTasks'); require('./tasks/snapTasks'); require('./tasks/signingTasks'); require('./tasks/profilingTasks'); +require('./tasks/insertionTasks'); diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 7de5ba59ba..0c74e9dfa0 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -8,6 +8,14 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import minimist from 'minimist'; +import { exec, spawnSync} from 'child_process'; +import { promisify } from 'util'; +import * as xml2js from 'xml2js'; +import { Octokit } from '@octokit/rest'; +import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; +import { PlatformInformation } from '../src/shared/platform'; + +const execAsync = promisify(exec); function logWarning(message: string): void { console.log(`##vso[task.logissue type=warning]${message}`); @@ -17,13 +25,26 @@ function logError(message: string): void { console.log(`##vso[task.logissue type=error]${message}`); } -import minimist from 'minimist'; -import { exec } from 'child_process'; -import { promisify } from 'util'; -import fetch from 'node-fetch'; -import * as AdmZip from 'adm-zip'; +async function git(args: string[], printCommand = true): Promise { + if (printCommand) { + console.log(`git ${args.join(' ')}`); + } -const execAsync = promisify(exec); + const git = spawnSync('git', args); + if (git.status != 0) { + const err = git.stderr.toString(); + if (printCommand) { + console.log(`Failed to execute git ${args.join(' ')}.`); + } + throw err; + } + + const stdout = git.stdout.toString(); + if (printCommand) { + console.log(stdout); + } + return stdout; +} interface InsertionOptions { roslynVersion?: string; @@ -57,7 +78,7 @@ gulp.task('insertion:roslyn', async (): Promise => { console.log(`New Roslyn version: ${newVersion}`); // Step 2: Get current SHA from package - const currentSHA = await getCurrentRoslynSHA(); + const currentSHA = await getCommitFromNugetAsync(allNugetPackages.roslyn); if (!currentSHA) { throw new Error('Could not determine current Roslyn SHA from package'); } @@ -126,59 +147,54 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { - const packageJsonContent = fs.readFileSync('package.json', 'utf8'); - const packageJson = JSON.parse(packageJsonContent); - const currentVersion = packageJson.defaults?.roslyn; - - if (!currentVersion) { - console.log('No roslyn version in package.json, this is first run'); +async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { + const packageJsonString = fs.readFileSync('./package.json').toString(); + const packageJson = JSON.parse(packageJsonString); + const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; + if (!packageVersion) { + logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); return null; } - - console.log(`Current Roslyn version: ${currentVersion}`); - - try { - const packageName = 'microsoft.codeanalysis.common'; - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), currentVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } + const platform = await PlatformInformation.GetCurrent(); + const vsixPlatformInfo = platformSpecificPackages.find( + (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture + )!; - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } + const packageName = packageInfo.getPackageName(vsixPlatformInfo); + console.log(`${packageName} version is ${packageVersion}`); - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*?)"/.exec(nuspecFile); - if (results == null || results.length === 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } + // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - if (results.length !== 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); + return null; + } - const commitNumber = results[1]; - console.log(`Found commit SHA: ${commitNumber}`); - return commitNumber; - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logError(`Error getting current SHA: ${errorMessage}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*)"/.exec(nuspecFile); + if (results == null || results.length == 0) { + logError('Failed to find commit number from nuspec file'); return null; } + + if (results.length != 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`commitNumber is ${commitNumber}`); + return commitNumber; } async function verifyRoslynRepo(roslynRepoPath: string): Promise { From cb782a8f62a0915bbfde41fc23accfe674524b52 Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Mon, 1 Sep 2025 21:19:58 +0530 Subject: [PATCH 07/12] extracted helper functions --- tasks/gitHelpers.ts | 196 ++++++++++++++++++++ tasks/insertionTasks.ts | 393 ++++++++++++++++++---------------------- 2 files changed, 370 insertions(+), 219 deletions(-) create mode 100644 tasks/gitHelpers.ts diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts new file mode 100644 index 0000000000..a7c20c9ba0 --- /dev/null +++ b/tasks/gitHelpers.ts @@ -0,0 +1,196 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { Octokit } from '@octokit/rest'; +import { NugetPackageInfo } from './offlinePackagingTasks'; +import { PlatformInformation } from '../src/shared/platform'; +import { platformSpecificPackages } from './offlinePackagingTasks'; + +export interface GitOptions { + userName?: string; + email?: string; + commitSha: string; + targetRemoteRepo: string; + baseBranch: string; +} + +export interface BranchAndPROptions extends GitOptions { + githubPAT: string; + dryRun?: boolean; + branchPrefix?: string; // optional prefix for branch names (defaults to 'localization') +} + +// Logging utilities +export function logWarning(message: string): void { + console.log(`##vso[task.logissue type=warning]${message}`); +} + +export function logError(message: string): void { + console.log(`##vso[task.logissue type=error]${message}`); +} + +export async function git(args: string[], printCommand = true): Promise { + if (printCommand) { + console.log(`git ${args.join(' ')}`); + } + + const git = spawnSync('git', args); + if (git.status !== 0) { + const err = git.stderr.toString(); + if (printCommand) { + console.log(`Failed to execute git ${args.join(' ')}.`); + } + throw err; + } + + const stdout = git.stdout.toString(); + if (printCommand) { + console.log(stdout); + } + return stdout; +} + +export async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { + try { + const packageJsonString = fs.readFileSync('./package.json').toString(); + const packageJson = JSON.parse(packageJsonString); + const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; + if (!packageVersion) { + logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); + return null; + } + + const platform = await PlatformInformation.GetCurrent(); + const vsixPlatformInfo = platformSpecificPackages.find( + (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture + )!; + + const packageName = packageInfo.getPackageName(vsixPlatformInfo); + console.log(`${packageName} version is ${packageVersion}`); + + // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); + + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); + return null; + } + + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*?)"/.exec(nuspecFile); + if (results == null || results.length === 0) { + logError('Failed to find commit number from nuspec file'); + return null; + } + + if (results.length !== 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`commitNumber is ${commitNumber}`); + return commitNumber; + } catch (error) { + logError(`Error getting commit from NuGet package: ${error}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } + throw error; + } +} + +export async function createBranchAndPR( + options: BranchAndPROptions, + title: string, + body?: string +): Promise { + const { githubPAT, targetRemoteRepo, baseBranch, dryRun } = options; + const remoteRepoAlias = 'target'; + const branchPrefix = options.branchPrefix || 'localization'; + const newBranchName = `${branchPrefix}/${options.commitSha}`; + + // Configure git user + if (options.userName && options.email) { + await git(['config', 'user.name', options.userName]); + await git(['config', 'user.email', options.email]); + } + + // Create and checkout new branch + await git(['checkout', '-b', newBranchName]); + + // Add and commit changes + await git(['add', '.']); + await git(['commit', '-m', title]); + + // Add remote and push + await git( + [ + 'remote', + 'add', + remoteRepoAlias, + `https://${options.userName}:${githubPAT}@github.com/dotnet/${targetRemoteRepo}.git`, + ], + false // Don't print command with PAT + ); + + await git(['fetch', remoteRepoAlias]); + + // Check if branch already exists on remote (refs/heads/) + const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/heads/' + newBranchName]); + if (lsRemote.trim() !== '') { + console.log(`##vso[task.logissue type=error]${newBranchName} already exists in ${targetRemoteRepo}. Skip pushing.`); + return; + } + + if (!dryRun) { + // Push the newly created branch to the target remote + await git(['push', '-u', remoteRepoAlias, newBranchName]); + } else { + console.log('[DRY RUN] Would have pushed branch to remote'); + } + + const octokit = new Octokit({ auth: githubPAT }); + + // Check for existing PRs with same title + const listPullRequest = await octokit.rest.pulls.list({ + owner: 'dotnet', + repo: targetRemoteRepo, + }); + + if (listPullRequest.status !== 200) { + throw `Failed to get response from GitHub, http status code: ${listPullRequest.status}`; + } + + if (listPullRequest.data.some(pr => pr.title === title)) { + console.log('Pull request with the same name already exists. Skip creation.'); + return; + } + + if (!dryRun) { + const pullRequest = await octokit.rest.pulls.create({ + body: body || title, + owner: 'dotnet', + repo: targetRemoteRepo, + title: title, + head: newBranchName, + base: baseBranch, + }); + console.log(`Created pull request: ${pullRequest.data.html_url}.`); + } else { + console.log(`[DRY RUN] Would have created PR with title: ${title}`); + } +} diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 0c74e9dfa0..cf5758f723 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -6,46 +6,16 @@ import * as gulp from 'gulp'; import * as fs from 'fs'; import * as path from 'path'; -import * as os from 'os'; import minimist from 'minimist'; -import { exec, spawnSync} from 'child_process'; +import { exec } from 'child_process'; import { promisify } from 'util'; import * as xml2js from 'xml2js'; -import { Octokit } from '@octokit/rest'; -import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; -import { PlatformInformation } from '../src/shared/platform'; +import { allNugetPackages} from './offlinePackagingTasks'; +import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR } from './gitHelpers'; +import * as os from 'os'; const execAsync = promisify(exec); -function logWarning(message: string): void { - console.log(`##vso[task.logissue type=warning]${message}`); -} - -function logError(message: string): void { - console.log(`##vso[task.logissue type=error]${message}`); -} - -async function git(args: string[], printCommand = true): Promise { - if (printCommand) { - console.log(`git ${args.join(' ')}`); - } - - const git = spawnSync('git', args); - if (git.status != 0) { - const err = git.stderr.toString(); - if (printCommand) { - console.log(`Failed to execute git ${args.join(' ')}.`); - } - throw err; - } - - const stdout = git.stdout.toString(); - if (printCommand) { - console.log(stdout); - } - return stdout; -} - interface InsertionOptions { roslynVersion?: string; roslynEndSHA?: string; @@ -62,7 +32,6 @@ gulp.task('insertion:roslyn', async (): Promise => { const options = minimist(process.argv.slice(2)); console.log('Starting Roslyn insertion process...'); - console.log(`Options: ${JSON.stringify(options, null, 2)}`); try { // Step 1: Extract Roslyn version from AssetManifest @@ -106,12 +75,30 @@ gulp.task('insertion:roslyn', async (): Promise => { const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); + // Check if PR list is empty or contains no meaningful PRs + if (!prList || prList === '(no PRs with required labels)') { + console.log('No PRs with required labels found. Skipping insertion.'); + logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); + return; + } + // Step 6: Update files await updatePackageJson(options.roslynVersion); await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); - // Step 6: Create branch and PR - await createBranchAndPR(options.roslynVersion, options.roslynEndSHA, options); + // Step 7: Create branch and PR + const prTitle = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; + const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSHA}).\n\n${prList}`; + + await createBranchAndPR({ + ...options, + commitSha: options.roslynEndSHA!, + targetRemoteRepo: 'vscode-csharp', + baseBranch: options.targetBranch || 'main', + branchPrefix: 'insertion', + githubPAT: options.githubPAT!, + dryRun: options.dryRun + }, prTitle, prBody); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -135,7 +122,6 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { - const packageJsonString = fs.readFileSync('./package.json').toString(); - const packageJson = JSON.parse(packageJsonString); - const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; - if (!packageVersion) { - logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); - return null; - } - - const platform = await PlatformInformation.GetCurrent(); - const vsixPlatformInfo = platformSpecificPackages.find( - (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture - )!; - - const packageName = packageInfo.getPackageName(vsixPlatformInfo); - console.log(`${packageName} version is ${packageVersion}`); - - // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } - - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } - - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*)"/.exec(nuspecFile); - if (results == null || results.length == 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } - - if (results.length != 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } - - const commitNumber = results[1]; - console.log(`commitNumber is ${commitNumber}`); - return commitNumber; -} - async function verifyRoslynRepo(roslynRepoPath: string): Promise { if (!fs.existsSync(roslynRepoPath)) { throw new Error(`Roslyn repository not found at ${roslynRepoPath}`); @@ -252,136 +188,155 @@ async function updatePackageJson(newVersion: string): Promise { } async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { - console.log('Updating CHANGELOG.md...'); - const changelogPath = 'CHANGELOG.md'; - let changelogContent = fs.readFileSync(changelogPath, 'utf8'); - - // Format PR list with proper indentation - const formattedPRList = prList.split('\n').map(line => ` ${line}`).join(os.EOL); - - // Find and update the Roslyn bump line - const lines = changelogContent.split(/\r?\n/); - const newLines: string[] = []; - let skipNext = false; - - for (let i = 0; i < lines.length; i++) { - if (skipNext && lines[i].startsWith(' *')) { - continue; - } - skipNext = false; - - if (lines[i].match(/^\* Bump Roslyn to/)) { - const prLink = buildNumber && buildId - ? `[${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; - newLines.push(`* Bump Roslyn to ${version} (PR: ${prLink})`); - if (prList !== '(no PRs with required labels)') { - newLines.push(formattedPRList); - } - skipNext = true; - } else { - newLines.push(lines[i]); - } - } - - fs.writeFileSync(changelogPath, newLines.join('\n')); -} + console.log('Updating CHANGELOG.md...'); + const changelogPath = 'CHANGELOG.md'; + const orig = fs.readFileSync(changelogPath, 'utf8'); -async function createBranchAndPR(version: string, endSHA: string, options: InsertionOptions): Promise { - try { - console.log('Creating and pushing branch...'); - - // Configure git user - await git(['config', '--local', 'user.name', 'Azure Pipelines']); - await git(['config', '--local', 'user.email', 'azuredevops@microsoft.com']); - - // Check for changes - const changedFiles = await git(['diff', '--name-only', 'HEAD']); - if (!changedFiles) { - console.log('No files changed, skipping branch creation'); - return; - } - - console.log(`Changed files: ${changedFiles}`); - - // Create and checkout new branch - const shortSHA = endSHA.substring(0, 8); - const branchName = `insertion/${shortSHA}`; - await git(['checkout', '-b', branchName]); - - // Stage and commit changes - await git(['add', 'package.json', 'CHANGELOG.md']); - await git(['commit', '-m', `Bump Roslyn to ${version} (${shortSHA})`]); - - // Configure remote with PAT for authentication - const pat = options.githubPAT; - if (!pat) { - throw new Error('GitHub PAT is required to push changes'); - } - - const remoteRepoAlias = 'targetRepo'; - await git( - [ - 'remote', - 'add', - remoteRepoAlias, - `https://${pat}@github.com/dotnet/vscode-csharp.git`, - ], - false // Don't log the command to avoid exposing the PAT - ); - - // Check if branch already exists - const lsRemote = await git(['ls-remote', '--heads', remoteRepoAlias, branchName]); - if (lsRemote.trim() !== '') { - console.log(`##vso[task.logissue type=warning]${branchName} already exists. Skipping push.`); - return; - } - - // Push the branch - await git(['push', '-u', remoteRepoAlias, branchName]); - - // Create PR if not in dry run mode - if (options.dryRun) { - console.log('Dry run: Would create PR for branch', branchName); - return; - } - - console.log('Creating pull request...'); - const octokit = new Octokit({ auth: pat }); - const title = `Bump Roslyn to ${version} (${shortSHA})`; - - // Check if PR already exists - const listPullRequest = await octokit.rest.pulls.list({ - owner: 'dotnet', - repo: 'vscode-csharp', - head: `dotnet:${branchName}`, - state: 'open' - }); - - if (listPullRequest.data.length > 0) { - console.log(`Pull request already exists: ${listPullRequest.data[0].html_url}`); - return; - } - - // Create the PR - const pr = await octokit.rest.pulls.create({ - owner: 'dotnet', - repo: 'vscode-csharp', - title: title, - head: branchName, - base: options.targetBranch || 'main', - body: `This is an automated PR to update the Roslyn version to ${version} (${shortSHA})`, - maintainer_can_modify: true - }); - - console.log(`Created pull request: ${pr.data.html_url}`); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logError(`Failed to create branch/PR: ${errorMessage}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } - throw error; - } + // Preserve original line endings + const originalHasCRLF = orig.indexOf('\r\n') !== -1; + const NL = os.EOL; + + // Normalize for processing + const text = orig.replace(/\r\n/g, NL); + + // Prepare PR list (filter out 'View Complete Diff' lines) + const formattedPRList = + prList + ? prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => ` ${line}`) + .join(NL) + : ''; + + // Find the first top-level header "# ..." + const topHeaderRegex = /^# .*/m; + const headerMatch = topHeaderRegex.exec(text); + if (!headerMatch) { + const prLink = buildNumber && buildId + ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + let roslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + if (formattedPRList && prList && prList !== '(no PRs with required labels)') { + roslynBlock += NL + formattedPRList; + } + roslynBlock += NL; + const out = originalHasCRLF ? (roslynBlock + text).replace(/\n/g, '\r\n') : roslynBlock + text; + fs.writeFileSync(changelogPath, out, 'utf8'); + console.log('CHANGELOG.md updated successfully'); + return; + } + + const headerStart = headerMatch.index; + const headerLineEnd = text.indexOf(NL, headerStart); + const headerLineEndIndex = headerLineEnd === -1 ? text.length : headerLineEnd; + + // Find end of this header section (next top-level header or EOF) + const nextTopHeaderRegex = /^# .*/gm; + nextTopHeaderRegex.lastIndex = headerLineEndIndex + 1; + const nextHeaderMatch = nextTopHeaderRegex.exec(text); + const sectionEndIndex = nextHeaderMatch ? nextHeaderMatch.index : text.length; + + const body = text.substring(headerLineEndIndex + 1, sectionEndIndex); + + // Split body into leading content + top-level bullet blocks (each starts with "* ") + const bulletStartRegex = /^\* /gm; + const starts: number[] = []; + let m: RegExpExecArray | null; + while ((m = bulletStartRegex.exec(body)) !== null) { + starts.push(m.index); + } + + let leading = ''; + let blocks: string[] = []; + if (starts.length === 0) { + leading = body; + blocks = []; + } else { + leading = body.slice(0, starts[0]); + for (let i = 0; i < starts.length; i++) { + const s = starts[i]; + const e = i + 1 < starts.length ? starts[i + 1] : body.length; + blocks.push(body.slice(s, e)); + } + } + + // Locate Roslyn and Razor blocks + let roslynIndex = -1; + let razorIndex = -1; + for (let i = 0; i < blocks.length; i++) { + const firstLine = blocks[i].split(NL, 1)[0].trim(); + if (/^\*\s*Bump\s+Roslyn\s+to/i.test(firstLine)) { + roslynIndex = i; + } else if (/^\*\s*Bump\s+Razor\s+to/i.test(firstLine)) { + razorIndex = i; + } + } + + // Prepare new Roslyn block + optional PR list + const prLink = buildNumber && buildId + ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + if (formattedPRList && prList && prList !== '(no PRs with required labels)') { + newRoslynBlock += NL + formattedPRList; + } + if (!newRoslynBlock.endsWith(NL)) { + newRoslynBlock += NL; + } + + // Rebuild blocks according to the rules + const newBlocks: string[] = []; + let roslynInserted = false; + + for (let i = 0; i < blocks.length; i++) { + if (i === roslynIndex) { + // Update version and PR link in place (preserving any existing bullet contents) + let updated = blocks[i].replace( + /^(\* Bump Roslyn to\s+)([^\s(]+)(?:\s*\(PR:[^)]+\))?/i, + `$1${version} (PR: ${prLink})` + ); + + // If we have a PR list and the existing block has no indented PR items, append it + if (formattedPRList && prList && prList !== '(no PRs with required labels)') { + if (!/\n\s{2}\*/.test(blocks[i]) && !updated.includes(formattedPRList)) { + if (!updated.endsWith(NL)) updated += NL; + updated += formattedPRList + NL; + } + } + + newBlocks.push(updated); + roslynInserted = true; + } else if (i === razorIndex) { + // Insert Roslyn before Razor if not already inserted + if (!roslynInserted) { + newBlocks.push(newRoslynBlock); + roslynInserted = true; + } + newBlocks.push(blocks[i]); + } else { + newBlocks.push(blocks[i]); + } + } + + // If Roslyn not inserted yet, place it at the top of the first section (after leading) + if (!roslynInserted) { + if (newBlocks.length > 0) { + newBlocks.unshift(newRoslynBlock); + } else { + // No bullet blocks found; append into leading + leading = leading + newRoslynBlock; + } + } + + // Assemble new body and full text + const rebuiltBlocks = newBlocks.join(NL); + const newBody = leading + rebuiltBlocks; + const newTextNormalized = text.slice(0, headerLineEndIndex + 1) + newBody + text.slice(sectionEndIndex); + + // Restore original newline style (normalize any newline to the original style) + const finalText = newTextNormalized.replace(/\r\n|\r|\n/g, originalHasCRLF ? '\r\n' : '\n'); + fs.writeFileSync(changelogPath, finalText, 'utf8'); + + console.log('CHANGELOG.md updated successfully'); } From 94aea42ea8ced535bce7bbb0a6cdd5c30174210a Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Tue, 2 Sep 2025 19:14:32 +0530 Subject: [PATCH 08/12] improved changelog updation logic --- tasks/insertionTasks.ts | 42 ++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index cf5758f723..7e2214f32a 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -200,19 +200,21 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st const text = orig.replace(/\r\n/g, NL); // Prepare PR list (filter out 'View Complete Diff' lines) - const formattedPRList = - prList - ? prList - .split(/\r?\n/) - .filter((l) => l.trim() && !l.includes('View Complete Diff')) - .map((line) => ` ${line}`) - .join(NL) - : ''; + // Normalize each PR line (trim) so we don't accidentally double-indent when inserting. + const prLines = prList + ? prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => line.trim()) + : []; + + const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; // Find the first top-level header "# ..." const topHeaderRegex = /^# .*/m; const headerMatch = topHeaderRegex.exec(text); if (!headerMatch) { + // No top-level header; prepend a Roslyn bump const prLink = buildNumber && buildId ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` : '[#TBD](TBD)'; @@ -297,11 +299,25 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st `$1${version} (PR: ${prLink})` ); - // If we have a PR list and the existing block has no indented PR items, append it - if (formattedPRList && prList && prList !== '(no PRs with required labels)') { - if (!/\n\s{2}\*/.test(blocks[i]) && !updated.includes(formattedPRList)) { - if (!updated.endsWith(NL)) updated += NL; - updated += formattedPRList + NL; + // If we have a PR list and the existing block does not already contain it, insert it before existing PR bullets. + if (prLines.length > 0 && prList && prList !== '(no PRs with required labels)') { + const firstPR = prLines[0]; + + // If the first PR is not already present in the block, insert our formatted PR list. + if (!updated.includes(firstPR)) { + // Try to locate the first existing PR bullet (lines like "\n * ...") + const prBulletIndex = updated.indexOf(NL + ' * '); + if (prBulletIndex !== -1) { + // Insert formatted PR list before the first existing PR bullet. + const insertPos = prBulletIndex + NL.length; // position just before the bullet line + const prefix = updated.slice(0, insertPos); + const suffix = updated.slice(insertPos); + updated = prefix + formattedPRList + NL + suffix; + } else { + // If no existing bullets found, append as before. + if (!updated.endsWith(NL)) updated += NL; + updated += formattedPRList + NL; + } } } From 4ff9705f90edaae0f0d1ce849f5a0f9a5047e217 Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Tue, 2 Sep 2025 19:43:44 +0530 Subject: [PATCH 09/12] downloading latest roslyn-tools --- azure-pipelines/dotnet-vscode-csharp-insertion.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index a26969a3e0..18f450f992 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -82,6 +82,14 @@ extends: git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git $(Pipeline.Workspace)/roslyn displayName: 'Clone Roslyn repository' + - script: | + echo "Installing roslyn-tools..." + dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json + echo "Verifying roslyn-tools installation..." + dotnet tool list -g + roslyn-tools --help + displayName: 'Install roslyn-tools CLI' + - task: Npm@1 displayName: Run Roslyn insertion env: From 94c8d620050321d273752cc6fc87b1ec1dadb8a5 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Thu, 4 Sep 2025 21:37:21 +0530 Subject: [PATCH 10/12] Pr reviews resolved --- .../dotnet-vscode-csharp-insertion.yml | 16 +- tasks/gitHelpers.ts | 27 +- tasks/insertionTasks.ts | 273 +++++------------- tasks/localizationTasks.ts | 81 ++---- 4 files changed, 119 insertions(+), 278 deletions(-) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index 18f450f992..b84c64c637 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -27,13 +27,14 @@ variables: value: $(resources.pipeline.officialBuildCI.runName) - name: RoslynBuildId value: $(resources.pipeline.officialBuildCI.runID) + - template: dotnet-variables.yml extends: template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: - name: AzurePipelines-EO - image: AzurePipelinesUbuntu22.04compliant + name: netcore1espool-internal + image: 1es-ubuntu-2204 os: linux stages: - stage: BumpRoslyn @@ -42,8 +43,8 @@ extends: - job: ProcessBump displayName: Process Roslyn Bump pool: - name: AzurePipelines-EO - image: AzurePipelinesUbuntu22.04compliant + name: netcore1espool-internal + image: 1es-ubuntu-2204 os: linux steps: - checkout: self @@ -52,12 +53,9 @@ extends: - task: UseDotNet@2 displayName: Install .NET SDK inputs: - version: 9.0.x + version: $(defaultDotnetVersion) - - task: NodeTool@0 - displayName: Install Node.js - inputs: - versionSpec: '20.x' + - template: install-node.yml - task: DownloadPipelineArtifact@2 displayName: Download Asset Manifests diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts index a7c20c9ba0..b76dffc753 100644 --- a/tasks/gitHelpers.ts +++ b/tasks/gitHelpers.ts @@ -12,8 +12,6 @@ import { PlatformInformation } from '../src/shared/platform'; import { platformSpecificPackages } from './offlinePackagingTasks'; export interface GitOptions { - userName?: string; - email?: string; commitSha: string; targetRemoteRepo: string; baseBranch: string; @@ -21,8 +19,10 @@ export interface GitOptions { export interface BranchAndPROptions extends GitOptions { githubPAT: string; - dryRun?: boolean; - branchPrefix?: string; // optional prefix for branch names (defaults to 'localization') + dryRun: boolean; + newBranchName: string; + userName?: string; + email?: string; } // Logging utilities @@ -116,17 +116,18 @@ export async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Pr export async function createBranchAndPR( options: BranchAndPROptions, title: string, + commitMessage: string, body?: string ): Promise { - const { githubPAT, targetRemoteRepo, baseBranch, dryRun } = options; + const { githubPAT, targetRemoteRepo, baseBranch, dryRun, userName, email, newBranchName } = options; const remoteRepoAlias = 'target'; - const branchPrefix = options.branchPrefix || 'localization'; - const newBranchName = `${branchPrefix}/${options.commitSha}`; - // Configure git user - if (options.userName && options.email) { - await git(['config', 'user.name', options.userName]); - await git(['config', 'user.email', options.email]); + // Set git user configuration if provided in options + if (userName) { + await git(['config', '--local', 'user.name', userName]); + } + if (email) { + await git(['config', '--local', 'user.email', email]); } // Create and checkout new branch @@ -134,7 +135,7 @@ export async function createBranchAndPR( // Add and commit changes await git(['add', '.']); - await git(['commit', '-m', title]); + await git(['commit', '-m', commitMessage]); // Add remote and push await git( @@ -191,6 +192,6 @@ export async function createBranchAndPR( }); console.log(`Created pull request: ${pullRequest.data.html_url}.`); } else { - console.log(`[DRY RUN] Would have created PR with title: ${title}`); + console.log(`[DRY RUN] Would have created PR with title: "${title}" and body: "${body || title}"`); } } diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 7e2214f32a..c40fa7ebe5 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -30,76 +30,79 @@ interface InsertionOptions { gulp.task('insertion:roslyn', async (): Promise => { const options = minimist(process.argv.slice(2)); - + console.log('Starting Roslyn insertion process...'); - + try { // Step 1: Extract Roslyn version from AssetManifest if (!options.assetManifestPath) { throw new Error('assetManifestPath is required'); } - + const newVersion = await extractRoslynVersionFromManifest(options.assetManifestPath); if (!newVersion) { throw new Error('Failed to extract Roslyn version from asset manifest'); } options.roslynVersion = newVersion; console.log(`New Roslyn version: ${newVersion}`); - + // Step 2: Get current SHA from package const currentSHA = await getCommitFromNugetAsync(allNugetPackages.roslyn); if (!currentSHA) { throw new Error('Could not determine current Roslyn SHA from package'); } console.log(`Current Roslyn SHA: ${currentSHA}`); - + // Step 3: Check if update needed if (!options.roslynEndSHA) { throw new Error('roslynEndSHA is required'); } - + if (currentSHA === options.roslynEndSHA) { console.log('No new commits to process - versions are identical'); return; } - + console.log(`Update needed: ${currentSHA}..${options.roslynEndSHA}`); - + // Step 4: Verify Roslyn repo exists if (!options.roslynRepoPath) { throw new Error('roslynRepoPath is required'); } await verifyRoslynRepo(options.roslynRepoPath); - + // Step 5: Generate PR list const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); - + // Check if PR list is empty or contains no meaningful PRs if (!prList || prList === '(no PRs with required labels)') { console.log('No PRs with required labels found. Skipping insertion.'); logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); return; } - + // Step 6: Update files await updatePackageJson(options.roslynVersion); await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); - + // Step 7: Create branch and PR const prTitle = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; - const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSHA}).\n\n${prList}`; - + const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSha}).\n\n${prList}`; + const commitMessage = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSha?.substring(0, 8)})`; + await createBranchAndPR({ ...options, commitSha: options.roslynEndSHA!, targetRemoteRepo: 'vscode-csharp', baseBranch: options.targetBranch || 'main', - branchPrefix: 'insertion', + newBranchName: `insertion/${options.roslynEndSHA}`, githubPAT: options.githubPAT!, - dryRun: options.dryRun - }, prTitle, prBody); - + dryRun: options.dryRun, + userName: options.userName, + email: options.email + }, prTitle, commitMessage, prBody); + } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Insertion failed: ${errorMessage}`); @@ -112,16 +115,16 @@ gulp.task('insertion:roslyn', async (): Promise => { async function extractRoslynVersionFromManifest(manifestPath: string): Promise { const xmlFile = path.join(manifestPath, 'OfficialBuild.xml'); - + if (!fs.existsSync(xmlFile)) { logError(`OfficialBuild.xml not found at ${xmlFile}`); return null; } - + const xmlContent = fs.readFileSync(xmlFile, 'utf8'); const parser = new xml2js.Parser(); const result = await parser.parseStringPromise(xmlContent); - + const packages = result?.Build?.Package || []; for (const pkg of packages) { const attrs = pkg.$; @@ -129,7 +132,7 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); - + // Setup auth for roslyn-tools const homeDir = process.env.HOME || process.env.USERPROFILE; const settingsDir = path.join(homeDir!, '.roslyn-tools'); if (!fs.existsSync(settingsDir)) { fs.mkdirSync(settingsDir, { recursive: true }); } - + const authJson = { GitHubToken: options.githubPAT || '', DevDivAzureDevOpsToken: '', @@ -157,20 +160,20 @@ async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: }; const settingsFile = path.join(settingsDir, 'settings'); fs.writeFileSync(settingsFile, Buffer.from(JSON.stringify(authJson)).toString('base64')); - + try { const { stdout } = await execAsync( `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); - return stdout || '(no PRs with required labels)'; + return stdout || '(failed to generate PR list, see pipeline for details)'; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logWarning(`PR finder failed, using empty list: ${errorMessage}`); if (error instanceof Error && error.stack) { console.log(`##[debug]${error.stack}`); } - return '(no PRs with required labels)'; + return '(failed to generate PR list, see pipeline for details)'; } } @@ -183,176 +186,56 @@ async function updatePackageJson(newVersion: string): Promise { throw new Error('Could not find defaults section in package.json'); } packageJson.defaults.roslyn = newVersion; - + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); } async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { - console.log('Updating CHANGELOG.md...'); - const changelogPath = 'CHANGELOG.md'; - const orig = fs.readFileSync(changelogPath, 'utf8'); - - // Preserve original line endings - const originalHasCRLF = orig.indexOf('\r\n') !== -1; - const NL = os.EOL; - - // Normalize for processing - const text = orig.replace(/\r\n/g, NL); - - // Prepare PR list (filter out 'View Complete Diff' lines) - // Normalize each PR line (trim) so we don't accidentally double-indent when inserting. - const prLines = prList - ? prList - .split(/\r?\n/) - .filter((l) => l.trim() && !l.includes('View Complete Diff')) - .map((line) => line.trim()) - : []; - - const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; - - // Find the first top-level header "# ..." - const topHeaderRegex = /^# .*/m; - const headerMatch = topHeaderRegex.exec(text); - if (!headerMatch) { - // No top-level header; prepend a Roslyn bump - const prLink = buildNumber && buildId - ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; - let roslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; - if (formattedPRList && prList && prList !== '(no PRs with required labels)') { - roslynBlock += NL + formattedPRList; - } - roslynBlock += NL; - const out = originalHasCRLF ? (roslynBlock + text).replace(/\n/g, '\r\n') : roslynBlock + text; - fs.writeFileSync(changelogPath, out, 'utf8'); - console.log('CHANGELOG.md updated successfully'); - return; - } - - const headerStart = headerMatch.index; - const headerLineEnd = text.indexOf(NL, headerStart); - const headerLineEndIndex = headerLineEnd === -1 ? text.length : headerLineEnd; - - // Find end of this header section (next top-level header or EOF) - const nextTopHeaderRegex = /^# .*/gm; - nextTopHeaderRegex.lastIndex = headerLineEndIndex + 1; - const nextHeaderMatch = nextTopHeaderRegex.exec(text); - const sectionEndIndex = nextHeaderMatch ? nextHeaderMatch.index : text.length; - - const body = text.substring(headerLineEndIndex + 1, sectionEndIndex); - - // Split body into leading content + top-level bullet blocks (each starts with "* ") - const bulletStartRegex = /^\* /gm; - const starts: number[] = []; - let m: RegExpExecArray | null; - while ((m = bulletStartRegex.exec(body)) !== null) { - starts.push(m.index); - } - - let leading = ''; - let blocks: string[] = []; - if (starts.length === 0) { - leading = body; - blocks = []; - } else { - leading = body.slice(0, starts[0]); - for (let i = 0; i < starts.length; i++) { - const s = starts[i]; - const e = i + 1 < starts.length ? starts[i + 1] : body.length; - blocks.push(body.slice(s, e)); - } - } - - // Locate Roslyn and Razor blocks - let roslynIndex = -1; - let razorIndex = -1; - for (let i = 0; i < blocks.length; i++) { - const firstLine = blocks[i].split(NL, 1)[0].trim(); - if (/^\*\s*Bump\s+Roslyn\s+to/i.test(firstLine)) { - roslynIndex = i; - } else if (/^\*\s*Bump\s+Razor\s+to/i.test(firstLine)) { - razorIndex = i; - } - } - - // Prepare new Roslyn block + optional PR list - const prLink = buildNumber && buildId - ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; - let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; - if (formattedPRList && prList && prList !== '(no PRs with required labels)') { - newRoslynBlock += NL + formattedPRList; - } - if (!newRoslynBlock.endsWith(NL)) { - newRoslynBlock += NL; - } - - // Rebuild blocks according to the rules - const newBlocks: string[] = []; - let roslynInserted = false; - - for (let i = 0; i < blocks.length; i++) { - if (i === roslynIndex) { - // Update version and PR link in place (preserving any existing bullet contents) - let updated = blocks[i].replace( - /^(\* Bump Roslyn to\s+)([^\s(]+)(?:\s*\(PR:[^)]+\))?/i, - `$1${version} (PR: ${prLink})` - ); - - // If we have a PR list and the existing block does not already contain it, insert it before existing PR bullets. - if (prLines.length > 0 && prList && prList !== '(no PRs with required labels)') { - const firstPR = prLines[0]; - - // If the first PR is not already present in the block, insert our formatted PR list. - if (!updated.includes(firstPR)) { - // Try to locate the first existing PR bullet (lines like "\n * ...") - const prBulletIndex = updated.indexOf(NL + ' * '); - if (prBulletIndex !== -1) { - // Insert formatted PR list before the first existing PR bullet. - const insertPos = prBulletIndex + NL.length; // position just before the bullet line - const prefix = updated.slice(0, insertPos); - const suffix = updated.slice(insertPos); - updated = prefix + formattedPRList + NL + suffix; - } else { - // If no existing bullets found, append as before. - if (!updated.endsWith(NL)) updated += NL; - updated += formattedPRList + NL; - } - } - } - - newBlocks.push(updated); - roslynInserted = true; - } else if (i === razorIndex) { - // Insert Roslyn before Razor if not already inserted - if (!roslynInserted) { - newBlocks.push(newRoslynBlock); - roslynInserted = true; - } - newBlocks.push(blocks[i]); - } else { - newBlocks.push(blocks[i]); - } - } - - // If Roslyn not inserted yet, place it at the top of the first section (after leading) - if (!roslynInserted) { - if (newBlocks.length > 0) { - newBlocks.unshift(newRoslynBlock); - } else { - // No bullet blocks found; append into leading - leading = leading + newRoslynBlock; - } - } - - // Assemble new body and full text - const rebuiltBlocks = newBlocks.join(NL); - const newBody = leading + rebuiltBlocks; - const newTextNormalized = text.slice(0, headerLineEndIndex + 1) + newBody + text.slice(sectionEndIndex); - - // Restore original newline style (normalize any newline to the original style) - const finalText = newTextNormalized.replace(/\r\n|\r|\n/g, originalHasCRLF ? '\r\n' : '\n'); - fs.writeFileSync(changelogPath, finalText, 'utf8'); - - console.log('CHANGELOG.md updated successfully'); + console.log('Updating CHANGELOG.md...'); + const changelogPath = 'CHANGELOG.md'; + const text = fs.readFileSync(changelogPath, 'utf8'); + const NL = os.EOL; + + // Prepare PR list (filter out 'View Complete Diff' lines) + const prLines = prList + ? prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => line.trim()) + : []; + + const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; + + // Find the first top-level header "# ..." + const topHeaderRegex = /^(# .*?)(\r?\n|$)/m; + const headerMatch = topHeaderRegex.exec(text); + if (!headerMatch) { + throw new Error('CHANGELOG.md must contain at least one top-level header (#)'); + } + + const headerEndLineIndex = headerMatch.index + headerMatch[0].length; + + // Prepare new Roslyn block + const prLink = buildNumber && buildId + ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + + let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + const shouldSkipPRList = prList === '(failed to generate PR list, see pipeline for details)'; + + if (!shouldSkipPRList && formattedPRList) { + newRoslynBlock += NL + formattedPRList; + } + newRoslynBlock += NL; // Ensure there's always a newline at the end + + // Insert the new block right after the header + const newText = + text.slice(0, headerEndLineIndex) + + NL + NL + // Add two newlines after the header + newRoslynBlock + + (text.length > headerEndLineIndex ? NL + text.slice(headerEndLineIndex) : ''); + + // Write the updated content back to the file + fs.writeFileSync(changelogPath, newText, 'utf8'); + console.log('CHANGELOG.md updated successfully'); } diff --git a/tasks/localizationTasks.ts b/tasks/localizationTasks.ts index bdb3d13c67..4fae9f9216 100644 --- a/tasks/localizationTasks.ts +++ b/tasks/localizationTasks.ts @@ -10,7 +10,7 @@ import { spawnSync } from 'node:child_process'; import * as path from 'path'; import * as util from 'node:util'; import { EOL } from 'node:os'; -import { Octokit } from '@octokit/rest'; +import { createBranchAndPR } from './gitHelpers'; type Options = { userName?: string; @@ -76,70 +76,29 @@ gulp.task('publish localization content', async () => { } console.log(`Changed files going to be staged: ${diff}`); - const newBranchName = `localization/${parsedArgs.commitSha}`; - // Make this optional so it can be tested locally by using dev's information. In real CI user name and email are always supplied. - if (parsedArgs.userName) { - await git(['config', '--local', 'user.name', parsedArgs.userName]); - } - if (parsedArgs.email) { - await git(['config', '--local', 'user.email', parsedArgs.email]); - } - - await git(['checkout', '-b', newBranchName]); - await git(['commit', '-m', `Localization result of ${parsedArgs.commitSha}.`]); - + const title = `Localization result based on ${parsedArgs.commitSha}`; + const commitMessage = `Localization result of ${parsedArgs.commitSha}`; const pat = process.env['GitHubPAT']; if (!pat) { throw 'No GitHub Pat found.'; } - - const remoteRepoAlias = 'targetRepo'; - await git( - [ - 'remote', - 'add', - remoteRepoAlias, - `https://${parsedArgs.userName}:${pat}@github.com/dotnet/${parsedArgs.targetRemoteRepo}.git`, - ], - // Note: don't print PAT to console - false - ); - await git(['fetch', remoteRepoAlias]); - - const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/head/' + newBranchName]); - if (lsRemote.trim() !== '') { - // If the localization branch of this commit already exists, don't try to create another one. - console.log( - `##vso[task.logissue type=error]${newBranchName} already exists in ${parsedArgs.targetRemoteRepo}. Skip pushing.` + try { + await createBranchAndPR( + { + commitSha: parsedArgs.commitSha, + targetRemoteRepo: parsedArgs.targetRemoteRepo, + baseBranch: parsedArgs.baseBranch, + githubPAT: process.env['GitHubPAT'] || '', + dryRun: false, + newBranchName: `localization/${parsedArgs.commitSha}`, + userName: parsedArgs.userName, + email: parsedArgs.email + }, + title, + commitMessage ); - } else { - await git(['push', '-u', remoteRepoAlias]); - } - - const octokit = new Octokit({ auth: pat }); - const listPullRequest = await octokit.rest.pulls.list({ - owner: 'dotnet', - repo: parsedArgs.targetRemoteRepo, - }); - - if (listPullRequest.status != 200) { - throw `Failed get response from GitHub, http status code: ${listPullRequest.status}`; - } - - const title = `Localization result based on ${parsedArgs.commitSha}`; - if (listPullRequest.data.some((pr) => pr.title === title)) { - console.log('Pull request with the same name already exists. Skip creation.'); - return; + } catch (error) { + console.error('Error creating branch and PR:', error); + throw error; } - - const pullRequest = await octokit.rest.pulls.create({ - body: title, - owner: 'dotnet', - repo: parsedArgs.targetRemoteRepo, - title: title, - head: newBranchName, - base: parsedArgs.baseBranch, - }); - - console.log(`Created pull request: ${pullRequest.data.html_url}.`); }); From d2ff303d4cf48d1df163cd85b8548e060efa68d1 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Thu, 4 Sep 2025 23:32:38 +0530 Subject: [PATCH 11/12] update: removed auth setup from roslyn-tools --- tasks/insertionTasks.ts | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index c40fa7ebe5..d67120f87d 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -25,7 +25,7 @@ interface InsertionOptions { roslynRepoPath?: string; targetBranch?: string; githubPAT?: string; - dryRun?: boolean; + dryRun: boolean; } gulp.task('insertion:roslyn', async (): Promise => { @@ -76,7 +76,7 @@ gulp.task('insertion:roslyn', async (): Promise => { console.log('PR List generated:', prList); // Check if PR list is empty or contains no meaningful PRs - if (!prList || prList === '(no PRs with required labels)') { + if (!prList || prList === '(failed to generate PR list, see pipeline for details)') { console.log('No PRs with required labels found. Skipping insertion.'); logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); return; @@ -146,21 +146,6 @@ async function verifyRoslynRepo(roslynRepoPath: string): Promise { async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); - // Setup auth for roslyn-tools - const homeDir = process.env.HOME || process.env.USERPROFILE; - const settingsDir = path.join(homeDir!, '.roslyn-tools'); - if (!fs.existsSync(settingsDir)) { - fs.mkdirSync(settingsDir, { recursive: true }); - } - - const authJson = { - GitHubToken: options.githubPAT || '', - DevDivAzureDevOpsToken: '', - DncEngAzureDevOpsToken: '' - }; - const settingsFile = path.join(settingsDir, 'settings'); - fs.writeFileSync(settingsFile, Buffer.from(JSON.stringify(authJson)).toString('base64')); - try { const { stdout } = await execAsync( `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, @@ -204,7 +189,14 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st .map((line) => line.trim()) : []; - const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; + // Format PR list as sub-items with proper indentation + const formattedPRList = prLines.length > 0 + ? prLines.map((line) => { + // If the line already starts with *, keep it, otherwise add it + const cleanLine = line.startsWith('*') ? line.substring(1).trim() : line; + return ` * ${cleanLine}`; + }).join(NL) + : ''; // Find the first top-level header "# ..." const topHeaderRegex = /^(# .*?)(\r?\n|$)/m; @@ -221,18 +213,20 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st : '[#TBD](TBD)'; let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; - const shouldSkipPRList = prList === '(failed to generate PR list, see pipeline for details)'; + + // Add PR list as sub-items if available and valid + const shouldSkipPRList = !prList || prList === '(failed to generate PR list, see pipeline for details)'; if (!shouldSkipPRList && formattedPRList) { newRoslynBlock += NL + formattedPRList; } - newRoslynBlock += NL; // Ensure there's always a newline at the end // Insert the new block right after the header const newText = text.slice(0, headerEndLineIndex) + NL + NL + // Add two newlines after the header newRoslynBlock + + NL + // Add a newline after the block (text.length > headerEndLineIndex ? NL + text.slice(headerEndLineIndex) : ''); // Write the updated content back to the file From 826c5041f42cb480a2dcb1077c2fb7411d6def96 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Mon, 8 Sep 2025 19:36:02 +0530 Subject: [PATCH 12/12] Bump Roslyn to 5.0.0-2.25430.3 (2eaa0989) --- .vscode/launch.json | 44 +- .../dotnet-vscode-csharp-insertion.yml | 10 +- package-lock.json | 390 ++++++++---------- package.json | 4 +- tasks/createTagsTasks.ts | 56 +-- tasks/gitHelpers.ts | 25 +- tasks/insertionTasks.ts | 98 +++-- test-manifests/OfficialBuild.xml | 1 + 8 files changed, 294 insertions(+), 334 deletions(-) create mode 100644 test-manifests/OfficialBuild.xml diff --git a/.vscode/launch.json b/.vscode/launch.json index 375f156e8c..641dc386a3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -265,6 +265,38 @@ "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js", "args": ["test:integration:razor:cohost"], "cwd": "${workspaceFolder}" + }, + { + "type": "node", + "request": "launch", + "name": "Debug Gulp Task", + "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js", + "args": ["${input:gulpTask}"], + "cwd": "${workspaceFolder}", + "sourceMaps": true, + "skipFiles": ["/**"] + }, + { + "type": "node", + "request": "launch", + "name": "Debug gulp insertion:roslyn", + "preLaunchTask": "build", + "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js", + "args": [ + "insertion:roslyn", + "--assetManifestPath=./test-manifests", + "--roslynRepoPath=../roslyn", + "--roslynEndSHA=2eaa09890be9bdc5ea662d54129991355e5c60bb", + "--roslynBuildNumber=5.0.0-2.25430.3", + "--roslynBuildId=2783326", + "--targetBranch=main", + "--githubPAT=${input:githubPAT}", + "--dryRun=false" + ], + "cwd": "${workspaceFolder}", + "console": "externalTerminal", + "sourceMaps": true, + "skipFiles": ["/**"] } ], "inputs": [ @@ -275,9 +307,15 @@ "options": ["singleCsproj", "RazorApp", "slnWithCsproj", "slnFilterWithCsproj", "slnWithGenerator"] }, { - "id": "gulpTaskName", - "description": "The name of the gulp task to debug", - "type": "promptString" + "id": "gulpTask", + "type": "promptString", + "description": "Enter the gulp task name to debug" + }, + { + "id": "githubPAT", + "type": "promptString", + "description": "Enter your GitHub Personal Access Token", + "password": true } ] } diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index b84c64c637..357a3c3646 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -13,6 +13,9 @@ resources: type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release + - repository: RoslynMirror + type: git + name: internal/dotnet-roslyn pipelines: - pipeline: officialBuildCI source: dotnet-roslyn-official @@ -75,10 +78,9 @@ extends: gulp installDependencies displayName: 'Install npm dependencies and gulp' - - script: | - echo "Cloning Roslyn repository..." - git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git $(Pipeline.Workspace)/roslyn - displayName: 'Clone Roslyn repository' + - checkout: RoslynMirror + displayName: 'Checkout Roslyn repository' + path: roslyn - script: | echo "Installing roslyn-tools..." diff --git a/package-lock.json b/package-lock.json index 540426363f..b21680515b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "vscode-languageserver-textdocument": "1.0.12", "vscode-languageserver-types": "3.17.6-next.6", "vscode-nls": "5.0.1", + "xml2js": "^0.6.2", "yauzl": "2.10.0" }, "devDependencies": { @@ -46,6 +47,7 @@ "@types/tmp": "0.0.33", "@types/uuid": "^9.0.1", "@types/vscode": "1.93.0", + "@types/xml2js": "^0.4.14", "@types/yauzl": "2.10.0", "@typescript-eslint/eslint-plugin": "^8.19.0", "@typescript-eslint/parser": "^8.19.0", @@ -63,7 +65,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-unicorn": "^47.0.0", "get-port": "5.1.1", - "gulp": "5.0.0", + "gulp": "^5.0.1", "jest": "^29.6.2", "jest-junit": "^16.0.0", "mock-http-server": "1.4.2", @@ -3672,6 +3674,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha1-XUYqKnMwNF4jCca1SaGDo3bej5o=", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/yargs/-/yargs-17.0.24.tgz", @@ -4197,6 +4209,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@vscode/l10n-dev/node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha1-2UQGMfuy7YACA/rRBvJyT2LEk7c=", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/@vscode/l10n-dev/node_modules/yargs": { "version": "17.7.2", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/yargs/-/yargs-17.7.2.tgz", @@ -4588,6 +4614,20 @@ "node": "20 || >=22" } }, + "node_modules/@vscode/vsce/node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha1-2UQGMfuy7YACA/rRBvJyT2LEk7c=", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/acorn/-/acorn-8.12.1.tgz", @@ -4940,9 +4980,9 @@ } }, "node_modules/b4a": { - "version": "1.6.6", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha1-pMw0mjhRmHw8SsLXeFwYdE9tqbo=", + "version": "1.6.7", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha1-qZWH1Ou/vVpuOyG9tdX6OFdnq+Q=", "dev": true, "license": "Apache-2.0" }, @@ -5128,9 +5168,9 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "node_modules/bare-events": { - "version": "2.3.1", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/bare-events/-/bare-events-2.3.1.tgz", - "integrity": "sha1-WvLuC+lXj4Hjwaqbw6aivPIjB84=", + "version": "2.6.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha1-95Oyi9w9zxR9fPAfiCpvCxLMxKI=", "dev": true, "license": "Apache-2.0", "optional": true @@ -5146,15 +5186,6 @@ "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/before-after-hook/-/before-after-hook-2.2.3.tgz", "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -5705,13 +5736,6 @@ "node": ">=0.8" } }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true, - "license": "MIT" - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/co/-/co-4.6.0.tgz", @@ -5856,16 +5880,6 @@ "node": ">= 10.13.0" } }, - "node_modules/copy-props/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/core-util-is/-/core-util-is-1.0.2.tgz", @@ -6250,16 +6264,6 @@ "node": ">= 10.13.0" } }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -6305,15 +6309,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/encodeurl/-/encodeurl-1.0.2.tgz", @@ -7693,16 +7688,6 @@ "node": ">= 10.13.0" } }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/flagged-respawn": { "version": "2.0.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/flagged-respawn/-/flagged-respawn-2.0.0.tgz", @@ -8142,9 +8127,9 @@ } }, "node_modules/glob-stream": { - "version": "8.0.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob-stream/-/glob-stream-8.0.2.tgz", - "integrity": "sha1-CeWBjkHBbdhSdNcsenFY0wdCYxM=", + "version": "8.0.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha1-h+YxU6rfBb0CB83holPuOdkUWLk=", "dev": true, "license": "MIT", "dependencies": { @@ -8290,16 +8275,16 @@ "dev": true }, "node_modules/gulp": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp/-/gulp-5.0.0.tgz", - "integrity": "sha1-ePS4rEigv2GzVNOeW+hE3ixcw/M=", + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp/-/gulp-5.0.1.tgz", + "integrity": "sha1-xD83qjRWnhAfttpOC0ZGdzBazTY=", "dev": true, "license": "MIT", "dependencies": { "glob-watcher": "^6.0.0", - "gulp-cli": "^3.0.0", + "gulp-cli": "^3.1.0", "undertaker": "^2.0.0", - "vinyl-fs": "^4.0.0" + "vinyl-fs": "^4.0.2" }, "bin": { "gulp": "bin/gulp.js" @@ -8309,9 +8294,9 @@ } }, "node_modules/gulp-cli": { - "version": "3.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp-cli/-/gulp-cli-3.0.0.tgz", - "integrity": "sha1-V3AI9TI/rWEGtE2ySAPCfDpkmEE=", + "version": "3.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp-cli/-/gulp-cli-3.1.0.tgz", + "integrity": "sha1-klkOmyCRQrF2yVrVxwZtJZIBcmg=", "dev": true, "license": "MIT", "dependencies": { @@ -8320,7 +8305,7 @@ "copy-props": "^4.0.0", "gulplog": "^2.2.0", "interpret": "^3.1.1", - "liftoff": "^5.0.0", + "liftoff": "^5.0.1", "mute-stdout": "^2.0.0", "replace-homedir": "^2.0.0", "semver-greatest-satisfied-range": "^2.0.0", @@ -9030,6 +9015,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-regex/-/is-regex-1.1.4.tgz", @@ -9204,6 +9199,7 @@ "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11341,9 +11337,9 @@ } }, "node_modules/liftoff": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/liftoff/-/liftoff-5.0.0.tgz", - "integrity": "sha1-Dl7SdbwzTK7A5VHs8IuyK+WD4jY=", + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/liftoff/-/liftoff-5.0.1.tgz", + "integrity": "sha1-4jKefx4Z6YyNunEYXyB45tu8XB8=", "dev": true, "license": "MIT", "dependencies": { @@ -11359,16 +11355,6 @@ "node": ">=10.13.0" } }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -12925,13 +12911,6 @@ } ] }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha1-9vB6yCwf1g+C4Ji0F6gOUvH0wUI=", - "dev": true, - "license": "MIT" - }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/random-bytes/-/random-bytes-1.0.0.tgz", @@ -13346,8 +13325,7 @@ "node_modules/sax": { "version": "1.2.4", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/semver": { "version": "7.5.4", @@ -13618,14 +13596,13 @@ "license": "MIT" }, "node_modules/streamx": { - "version": "2.18.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/streamx/-/streamx-2.18.0.tgz", - "integrity": "sha1-W8GlHrQSpmfr/c1ObPam/GVyGsc=", + "version": "2.22.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha1-yXy7DOGNpPTbWpcdyato/13H9aU=", "dev": true, "license": "MIT", "dependencies": { "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", "text-decoder": "^1.1.0" }, "optionalDependencies": { @@ -13909,9 +13886,9 @@ } }, "node_modules/text-decoder": { - "version": "1.1.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/text-decoder/-/text-decoder-1.1.0.tgz", - "integrity": "sha1-M3nnKPz004k+wa6jXowsrCFe8ZA=", + "version": "1.2.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha1-sZ2jZNmBsjJtX0MJnDEMyA13DGU=", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -14453,14 +14430,13 @@ } }, "node_modules/vinyl": { - "version": "3.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha1-EeFHMr9W4vqpj/3lFX/mwTJZ/zA=", + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha1-X1/4UlW9orXaJeSzvYCz/Ad/tak=", "dev": true, "license": "MIT", "dependencies": { "clone": "^2.1.2", - "clone-stats": "^1.0.0", "remove-trailing-separator": "^1.1.0", "replace-ext": "^2.0.0", "teex": "^1.0.1" @@ -14543,14 +14519,14 @@ } }, "node_modules/vinyl-fs": { - "version": "4.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl-fs/-/vinyl-fs-4.0.0.tgz", - "integrity": "sha1-Bss278kRxuEoRS8jC5ZYSpEzw6E=", + "version": "4.0.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha1-1GVXZT5KcQnynWJqnPR4aAx/jHA=", "dev": true, "license": "MIT", "dependencies": { "fs-mkdirp-stream": "^2.0.1", - "glob-stream": "^8.0.0", + "glob-stream": "^8.0.3", "graceful-fs": "^4.2.11", "iconv-lite": "^0.6.3", "is-valid-glob": "^1.0.0", @@ -14561,7 +14537,7 @@ "streamx": "^2.14.0", "to-through": "^3.0.0", "value-or-function": "^4.0.0", - "vinyl": "^3.0.0", + "vinyl": "^3.0.1", "vinyl-sourcemap": "^2.0.0" }, "engines": { @@ -14746,8 +14722,9 @@ "node_modules/which": { "version": "1.3.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -14931,10 +14908,10 @@ "dev": true }, "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, + "version": "0.6.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha1-3QtjAIOqCcFh4lpNCQHisqkptJk=", + "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -14947,7 +14924,6 @@ "version": "11.0.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -17776,6 +17752,15 @@ "integrity": "sha1-HNdXPgJyrvnDV7r8Y1thd8FUAT4=", "dev": true }, + "@types/xml2js": { + "version": "0.4.14", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha1-XUYqKnMwNF4jCca1SaGDo3bej5o=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "17.0.24", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/yargs/-/yargs-17.0.24.tgz", @@ -18130,6 +18115,16 @@ "integrity": "sha1-+NP30OxMPeo1p+PI76TLi0XJ5+4=", "dev": true }, + "xml2js": { + "version": "0.5.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha1-2UQGMfuy7YACA/rRBvJyT2LEk7c=", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, "yargs": { "version": "17.7.2", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/yargs/-/yargs-17.7.2.tgz", @@ -18312,6 +18307,16 @@ "dev": true } } + }, + "xml2js": { + "version": "0.5.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha1-2UQGMfuy7YACA/rRBvJyT2LEk7c=", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } } } }, @@ -18654,9 +18659,9 @@ } }, "b4a": { - "version": "1.6.6", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha1-pMw0mjhRmHw8SsLXeFwYdE9tqbo=", + "version": "1.6.7", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha1-qZWH1Ou/vVpuOyG9tdX6OFdnq+Q=", "dev": true }, "babel-jest": { @@ -18797,9 +18802,9 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bare-events": { - "version": "2.3.1", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/bare-events/-/bare-events-2.3.1.tgz", - "integrity": "sha1-WvLuC+lXj4Hjwaqbw6aivPIjB84=", + "version": "2.6.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha1-95Oyi9w9zxR9fPAfiCpvCxLMxKI=", "dev": true, "optional": true }, @@ -18814,12 +18819,6 @@ "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/before-after-hook/-/before-after-hook-2.2.3.tgz", "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" }, - "big.js": { - "version": "5.2.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -19228,12 +19227,6 @@ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, "co": { "version": "4.6.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/co/-/co-4.6.0.tgz", @@ -19352,14 +19345,6 @@ "requires": { "each-props": "^3.0.0", "is-plain-object": "^5.0.0" - }, - "dependencies": { - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true - } } }, "core-util-is": { @@ -19623,14 +19608,6 @@ "requires": { "is-plain-object": "^5.0.0", "object.defaults": "^1.1.0" - }, - "dependencies": { - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true - } } }, "eastasianwidth": { @@ -19672,12 +19649,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, "encodeurl": { "version": "1.0.2", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/encodeurl/-/encodeurl-1.0.2.tgz", @@ -20708,14 +20679,6 @@ "object.defaults": "^1.1.0", "object.pick": "^1.3.0", "parse-filepath": "^1.0.2" - }, - "dependencies": { - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true - } } }, "flagged-respawn": { @@ -21023,9 +20986,9 @@ } }, "glob-stream": { - "version": "8.0.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob-stream/-/glob-stream-8.0.2.tgz", - "integrity": "sha1-CeWBjkHBbdhSdNcsenFY0wdCYxM=", + "version": "8.0.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha1-h+YxU6rfBb0CB83holPuOdkUWLk=", "dev": true, "requires": { "@gulpjs/to-absolute-glob": "^4.0.0", @@ -21128,21 +21091,21 @@ "dev": true }, "gulp": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp/-/gulp-5.0.0.tgz", - "integrity": "sha1-ePS4rEigv2GzVNOeW+hE3ixcw/M=", + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp/-/gulp-5.0.1.tgz", + "integrity": "sha1-xD83qjRWnhAfttpOC0ZGdzBazTY=", "dev": true, "requires": { "glob-watcher": "^6.0.0", - "gulp-cli": "^3.0.0", + "gulp-cli": "^3.1.0", "undertaker": "^2.0.0", - "vinyl-fs": "^4.0.0" + "vinyl-fs": "^4.0.2" } }, "gulp-cli": { - "version": "3.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp-cli/-/gulp-cli-3.0.0.tgz", - "integrity": "sha1-V3AI9TI/rWEGtE2ySAPCfDpkmEE=", + "version": "3.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gulp-cli/-/gulp-cli-3.1.0.tgz", + "integrity": "sha1-klkOmyCRQrF2yVrVxwZtJZIBcmg=", "dev": true, "requires": { "@gulpjs/messages": "^1.1.0", @@ -21150,7 +21113,7 @@ "copy-props": "^4.0.0", "gulplog": "^2.2.0", "interpret": "^3.1.1", - "liftoff": "^5.0.0", + "liftoff": "^5.0.1", "mute-stdout": "^2.0.0", "replace-homedir": "^2.0.0", "semver-greatest-satisfied-range": "^2.0.0", @@ -21626,6 +21589,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", + "dev": true + }, "is-regex": { "version": "1.1.4", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-regex/-/is-regex-1.1.4.tgz", @@ -23342,9 +23311,9 @@ } }, "liftoff": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/liftoff/-/liftoff-5.0.0.tgz", - "integrity": "sha1-Dl7SdbwzTK7A5VHs8IuyK+WD4jY=", + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/liftoff/-/liftoff-5.0.1.tgz", + "integrity": "sha1-4jKefx4Z6YyNunEYXyB45tu8XB8=", "dev": true, "requires": { "extend": "^3.0.2", @@ -23354,14 +23323,6 @@ "is-plain-object": "^5.0.0", "rechoir": "^0.8.0", "resolve": "^1.20.0" - }, - "dependencies": { - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha1-RCf1CrNCnpAl6n1S6QQ6nvQVk0Q=", - "dev": true - } } }, "lines-and-columns": { @@ -24540,12 +24501,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "queue-tick": { - "version": "1.0.1", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha1-9vB6yCwf1g+C4Ji0F6gOUvH0wUI=", - "dev": true - }, "random-bytes": { "version": "1.0.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/random-bytes/-/random-bytes-1.0.0.tgz", @@ -24852,8 +24807,7 @@ "sax": { "version": "1.2.4", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { "version": "7.5.4", @@ -25053,14 +25007,13 @@ "dev": true }, "streamx": { - "version": "2.18.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/streamx/-/streamx-2.18.0.tgz", - "integrity": "sha1-W8GlHrQSpmfr/c1ObPam/GVyGsc=", + "version": "2.22.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha1-yXy7DOGNpPTbWpcdyato/13H9aU=", "dev": true, "requires": { "bare-events": "^2.2.0", "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", "text-decoder": "^1.1.0" } }, @@ -25279,9 +25232,9 @@ } }, "text-decoder": { - "version": "1.1.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/text-decoder/-/text-decoder-1.1.0.tgz", - "integrity": "sha1-M3nnKPz004k+wa6jXowsrCFe8ZA=", + "version": "1.2.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha1-sZ2jZNmBsjJtX0MJnDEMyA13DGU=", "dev": true, "requires": { "b4a": "^1.6.4" @@ -25659,13 +25612,12 @@ "dev": true }, "vinyl": { - "version": "3.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha1-EeFHMr9W4vqpj/3lFX/mwTJZ/zA=", + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha1-X1/4UlW9orXaJeSzvYCz/Ad/tak=", "dev": true, "requires": { "clone": "^2.1.2", - "clone-stats": "^1.0.0", "remove-trailing-separator": "^1.1.0", "replace-ext": "^2.0.0", "teex": "^1.0.1" @@ -25722,13 +25674,13 @@ } }, "vinyl-fs": { - "version": "4.0.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl-fs/-/vinyl-fs-4.0.0.tgz", - "integrity": "sha1-Bss278kRxuEoRS8jC5ZYSpEzw6E=", + "version": "4.0.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha1-1GVXZT5KcQnynWJqnPR4aAx/jHA=", "dev": true, "requires": { "fs-mkdirp-stream": "^2.0.1", - "glob-stream": "^8.0.0", + "glob-stream": "^8.0.3", "graceful-fs": "^4.2.11", "iconv-lite": "^0.6.3", "is-valid-glob": "^1.0.0", @@ -25739,7 +25691,7 @@ "streamx": "^2.14.0", "to-through": "^3.0.0", "value-or-function": "^4.0.0", - "vinyl": "^3.0.0", + "vinyl": "^3.0.1", "vinyl-sourcemap": "^2.0.0" }, "dependencies": { @@ -25891,7 +25843,7 @@ "which": { "version": "1.3.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, "requires": { "isexe": "^2.0.0" @@ -26026,10 +25978,9 @@ "dev": true }, "xml2js": { - "version": "0.5.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, + "version": "0.6.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha1-3QtjAIOqCcFh4lpNCQHisqkptJk=", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -26038,8 +25989,7 @@ "xmlbuilder": { "version": "11.0.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index 7ef1eab978..c0ab9688fc 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "workspace" ], "defaults": { - "roslyn": "5.0.0-2.25412.5", - "omniSharp": "1.39.14", + "roslyn": "5.0.0-2.25430.3", + "omniSharp": "1.39.14", "razor": "10.0.0-preview.25419.3", "razorOmnisharp": "7.0.0-preview.23363.1", "xamlTools": "17.14.36106.43" diff --git a/tasks/createTagsTasks.ts b/tasks/createTagsTasks.ts index a5589e9d74..e92bde07d4 100644 --- a/tasks/createTagsTasks.ts +++ b/tasks/createTagsTasks.ts @@ -4,12 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as gulp from 'gulp'; -import * as fs from 'fs'; import minimist from 'minimist'; import { Octokit } from '@octokit/rest'; -import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; -import { PlatformInformation } from '../src/shared/platform'; -import path from 'path'; +import { allNugetPackages } from './offlinePackagingTasks'; +import { getCommitFromNugetAsync } from './gitHelpers'; interface CreateTagsOptions { releaseVersion: string; @@ -194,53 +192,3 @@ function logWarning(message: string): void { function logError(message: string): void { console.log(`##vso[task.logissue type=error]${message}`); } - -async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { - const packageJsonString = fs.readFileSync('./package.json').toString(); - const packageJson = JSON.parse(packageJsonString); - const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; - if (!packageVersion) { - logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); - return null; - } - - const platform = await PlatformInformation.GetCurrent(); - const vsixPlatformInfo = platformSpecificPackages.find( - (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture - )!; - - const packageName = packageInfo.getPackageName(vsixPlatformInfo); - console.log(`${packageName} version is ${packageVersion}`); - - // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } - - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } - - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*)"/.exec(nuspecFile); - if (results == null || results.length == 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } - - if (results.length != 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } - - const commitNumber = results[1]; - console.log(`commitNumber is ${commitNumber}`); - return commitNumber; -} diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts index b76dffc753..f5e7282b7c 100644 --- a/tasks/gitHelpers.ts +++ b/tasks/gitHelpers.ts @@ -7,9 +7,8 @@ import { spawnSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import { Octokit } from '@octokit/rest'; -import { NugetPackageInfo } from './offlinePackagingTasks'; +import { NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; import { PlatformInformation } from '../src/shared/platform'; -import { platformSpecificPackages } from './offlinePackagingTasks'; export interface GitOptions { commitSha: string; @@ -118,7 +117,7 @@ export async function createBranchAndPR( title: string, commitMessage: string, body?: string -): Promise { +): Promise { const { githubPAT, targetRemoteRepo, baseBranch, dryRun, userName, email, newBranchName } = options; const remoteRepoAlias = 'target'; @@ -143,7 +142,7 @@ export async function createBranchAndPR( 'remote', 'add', remoteRepoAlias, - `https://${options.userName}:${githubPAT}@github.com/dotnet/${targetRemoteRepo}.git`, + `https://github.com/deepakrathore33/vscode-csharp.git`, ], false // Don't print command with PAT ); @@ -154,10 +153,10 @@ export async function createBranchAndPR( const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/heads/' + newBranchName]); if (lsRemote.trim() !== '') { console.log(`##vso[task.logissue type=error]${newBranchName} already exists in ${targetRemoteRepo}. Skip pushing.`); - return; + return null; } - if (!dryRun) { + if (dryRun !== true) { // Push the newly created branch to the target remote await git(['push', '-u', remoteRepoAlias, newBranchName]); } else { @@ -168,8 +167,8 @@ export async function createBranchAndPR( // Check for existing PRs with same title const listPullRequest = await octokit.rest.pulls.list({ - owner: 'dotnet', - repo: targetRemoteRepo, + owner: 'deepakrathore33', + repo: 'vscode-csharp', }); if (listPullRequest.status !== 200) { @@ -178,20 +177,22 @@ export async function createBranchAndPR( if (listPullRequest.data.some(pr => pr.title === title)) { console.log('Pull request with the same name already exists. Skip creation.'); - return; + return null; } - if (!dryRun) { + if (dryRun !== true) { const pullRequest = await octokit.rest.pulls.create({ body: body || title, - owner: 'dotnet', - repo: targetRemoteRepo, + owner: 'deepakrathore33', + repo: 'vscode-csharp', title: title, head: newBranchName, base: baseBranch, }); console.log(`Created pull request: ${pullRequest.data.html_url}.`); + return pullRequest.data.number; } else { console.log(`[DRY RUN] Would have created PR with title: "${title}" and body: "${body || title}"`); + return null; } } diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index d67120f87d..6bc1e40f8d 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -11,7 +11,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import * as xml2js from 'xml2js'; import { allNugetPackages} from './offlinePackagingTasks'; -import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR } from './gitHelpers'; +import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR, git } from './gitHelpers'; import * as os from 'os'; const execAsync = promisify(exec); @@ -75,23 +75,34 @@ gulp.task('insertion:roslyn', async (): Promise => { const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); - // Check if PR list is empty or contains no meaningful PRs - if (!prList || prList === '(failed to generate PR list, see pipeline for details)') { - console.log('No PRs with required labels found. Skipping insertion.'); - logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); + // Check if PR list is null or empty (generation failed or no matching PRs) + if (!prList) { + console.log('No PRs with required labels found or PR list generation failed. Skipping insertion.'); + logWarning('No PRs with VSCode label found between the commits or PR list generation failed. Skipping insertion.'); return; } // Step 6: Update files await updatePackageJson(options.roslynVersion); - await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); // Step 7: Create branch and PR const prTitle = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; - const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSha}).\n\n${prList}`; - const commitMessage = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSha?.substring(0, 8)})`; - await createBranchAndPR({ + // Include build information in the PR description + let prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSHA}).\n\n`; + + // Add build link if build information is available + if (options.roslynBuildNumber && options.roslynBuildId) { + prBody += `Build: [#${options.roslynBuildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${options.roslynBuildId})\n\n`; + } + + // Add PR list + prBody += prList; + + const commitMessage = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; + + // Create the PR + const prNumber = await createBranchAndPR({ ...options, commitSha: options.roslynEndSHA!, targetRemoteRepo: 'vscode-csharp', @@ -103,6 +114,21 @@ gulp.task('insertion:roslyn', async (): Promise => { email: options.email }, prTitle, commitMessage, prBody); + // If PR was created and we're not in dry run mode, update the changelog with the PR number + if (prNumber && !options.dryRun) { + console.log(`PR #${prNumber} created. Updating changelog with PR link...`); + + // Update changelog with PR number (single call) + await updateChangelog(options.roslynVersion, prList, prNumber); + + // Create a second commit to include the updated changelog and push to update the PR + await git(['add', 'CHANGELOG.md']); + await git(['commit', '-m', `Update changelog with PR #${prNumber}`]); + await git(['push', 'target', `insertion/${options.roslynEndSHA}`]); + + console.log(`Changelog updated with PR #${prNumber} link.`); + } + } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Insertion failed: ${errorMessage}`); @@ -133,6 +159,7 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { console.log(`Using Roslyn repository at ${roslynRepoPath}`); } -async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { +async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, _options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); try { @@ -151,14 +178,14 @@ async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); - return stdout || '(failed to generate PR list, see pipeline for details)'; + return stdout && stdout.trim() ? stdout : null; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - logWarning(`PR finder failed, using empty list: ${errorMessage}`); + logWarning(`PR finder failed: ${errorMessage}`); if (error instanceof Error && error.stack) { console.log(`##[debug]${error.stack}`); } - return '(failed to generate PR list, see pipeline for details)'; + return null; } } @@ -175,29 +202,12 @@ async function updatePackageJson(newVersion: string): Promise { fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); } -async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { +async function updateChangelog(version: string, prList: string | null, prNumber?: number): Promise { console.log('Updating CHANGELOG.md...'); const changelogPath = 'CHANGELOG.md'; const text = fs.readFileSync(changelogPath, 'utf8'); const NL = os.EOL; - // Prepare PR list (filter out 'View Complete Diff' lines) - const prLines = prList - ? prList - .split(/\r?\n/) - .filter((l) => l.trim() && !l.includes('View Complete Diff')) - .map((line) => line.trim()) - : []; - - // Format PR list as sub-items with proper indentation - const formattedPRList = prLines.length > 0 - ? prLines.map((line) => { - // If the line already starts with *, keep it, otherwise add it - const cleanLine = line.startsWith('*') ? line.substring(1).trim() : line; - return ` * ${cleanLine}`; - }).join(NL) - : ''; - // Find the first top-level header "# ..." const topHeaderRegex = /^(# .*?)(\r?\n|$)/m; const headerMatch = topHeaderRegex.exec(text); @@ -208,17 +218,27 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st const headerEndLineIndex = headerMatch.index + headerMatch[0].length; // Prepare new Roslyn block - const prLink = buildNumber && buildId - ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; + let newRoslynBlock = `* Bump Roslyn to ${version}`; - let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + // Add PR number if available + if (prNumber) { + newRoslynBlock = `* Bump Roslyn to ${version} (PR: [#${prNumber}](https://github.com/dotnet/vscode-csharp/pull/${prNumber}))`; + } - // Add PR list as sub-items if available and valid - const shouldSkipPRList = !prList || prList === '(failed to generate PR list, see pipeline for details)'; + // Add PR list as sub-items if available + if (prList) { + const prLines = prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => line.trim()); - if (!shouldSkipPRList && formattedPRList) { - newRoslynBlock += NL + formattedPRList; + const formattedPRList = prLines.length > 0 + ? prLines.map((line) => ` ${line}`).join(NL) + : ''; + + if (formattedPRList) { + newRoslynBlock += NL + formattedPRList; + } } // Insert the new block right after the header diff --git a/test-manifests/OfficialBuild.xml b/test-manifests/OfficialBuild.xml new file mode 100644 index 0000000000..3d7fc46fd9 --- /dev/null +++ b/test-manifests/OfficialBuild.xml @@ -0,0 +1 @@ + \ No newline at end of file