diff --git a/.github/workflows/pr-build-dll.yaml b/.github/workflows/pr-build-dll.yaml new file mode 100644 index 0000000..197e718 --- /dev/null +++ b/.github/workflows/pr-build-dll.yaml @@ -0,0 +1,105 @@ +name: "PR - Build SourceGenerator DLL" + +on: + pull_request: + paths-ignore: + - '**/*.md' + - '**/*.gitignore' + - '**/*.gitattributes' + +permissions: + contents: read + pull-requests: write + +jobs: + build-sourcegenerator: + name: Build and Upload SourceGenerator DLL + runs-on: ubuntu-latest + env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: true + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + + - name: Restore + run: dotnet restore + + - name: Build SourceGenerator (Release) + run: dotnet build Arch.SystemGroups.SourceGenerator/Arch.SystemGroups.SourceGenerator.csproj --configuration Release --no-restore + + - name: Upload SourceGenerator DLL + uses: actions/upload-artifact@v4 + with: + name: Arch.SystemGroups.SourceGenerator-PR${{ github.event.pull_request.number }}-run${{ github.run_number }} + path: Arch.SystemGroups.SourceGenerator/bin/Release/netstandard2.0/Arch.SystemGroups.SourceGenerator.dll + retention-days: 30 + + - name: Comment on PR with artifact link + uses: actions/github-script@v7 + with: + script: | + const artifactName = `Arch.SystemGroups.SourceGenerator-PR${{ github.event.pull_request.number }}-run${{ github.run_number }}`; + const runId = context.runId; + const runNumber = '${{ github.run_number }}'; + const repo = context.repo; + + const comment = `## โœ… SourceGenerator DLL Built Successfully + + The SourceGenerator DLL has been built in Release mode and is available for download. + + **๐Ÿ“ฆ Download:** [${artifactName}](https://github.com/${repo.owner}/${repo.repo}/actions/runs/${runId}) + + ### How to Download: + 1. Click the link above (requires GitHub login) + 2. Scroll to the "Artifacts" section at the bottom + 3. Click the artifact name to download + 4. **Extract the zip file** to get the DLL (GitHub artifacts are automatically zipped) + + > **Note:** PR artifacts are zipped by GitHub. For direct DLL downloads without extraction, use the [Release workflow](https://github.com/${repo.owner}/${repo.repo}/actions/workflows/release.yaml) which attaches DLLs directly to releases. + + **Build Details:** + - Configuration: Release + - Target Framework: netstandard2.0 + - DLL: \`Arch.SystemGroups.SourceGenerator.dll\` + - Workflow Run: [#${runId}](https://github.com/${repo.owner}/${repo.repo}/actions/runs/${runId}) + - Run Number: ${runNumber} + `; + + // Check if we already commented + const { data: comments } = await github.rest.issues.listComments({ + owner: repo.owner, + repo: repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('SourceGenerator DLL Built Successfully') + ); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: repo.owner, + repo: repo.repo, + comment_id: botComment.id, + body: comment + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: repo.owner, + repo: repo.repo, + issue_number: context.issue.number, + body: comment + }); + } diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..494debc --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,189 @@ +name: "Create Release" + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g., v1.0.0)' + required: true + type: string + custom_description: + description: 'Custom release description (optional - leave empty to auto-generate from PRs)' + required: false + type: string + prerelease: + description: 'Mark as pre-release' + required: false + type: boolean + default: false + +permissions: + contents: write + pull-requests: read + +jobs: + create-release: + name: Build and Create Release + runs-on: ubuntu-latest + env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: true + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for changelog generation + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + + - name: Restore + run: dotnet restore + + - name: Build SourceGenerator (Release) + run: dotnet build Arch.SystemGroups.SourceGenerator/Arch.SystemGroups.SourceGenerator.csproj --configuration Release --no-restore + + - name: Run Tests + run: dotnet test --configuration Release --no-restore --logger trx --results-directory "TestResults" + + - name: Generate Release Notes + id: release_notes + uses: actions/github-script@v7 + with: + result-encoding: string + script: | + const customDescription = `${{ github.event.inputs.custom_description }}`; + + // If custom description provided, use it + if (customDescription && customDescription.trim() !== '') { + return customDescription; + } + + // Otherwise, generate from merged PRs + const repo = context.repo; + + // Get the latest release + let lastReleaseDate; + try { + const { data: latestRelease } = await github.rest.repos.getLatestRelease({ + owner: repo.owner, + repo: repo.repo, + }); + lastReleaseDate = latestRelease.created_at; + } catch (error) { + // No previous release, use repo creation date or a far back date + lastReleaseDate = '2020-01-01T00:00:00Z'; + } + + // Get merged PRs since last release + const { data: pullRequests } = await github.rest.pulls.list({ + owner: repo.owner, + repo: repo.repo, + state: 'closed', + sort: 'updated', + direction: 'desc', + per_page: 100 + }); + + const mergedPRs = pullRequests.filter(pr => + pr.merged_at && + new Date(pr.merged_at) > new Date(lastReleaseDate) + ); + + if (mergedPRs.length === 0) { + return '## Changes\n\nNo merged pull requests since last release.'; + } + + // Group PRs by type based on title/labels + const features = []; + const bugFixes = []; + const other = []; + + for (const pr of mergedPRs) { + const title = pr.title.toLowerCase(); + const labels = pr.labels.map(l => l.name.toLowerCase()); + + const prLink = `- ${pr.title} (#${pr.number}) @${pr.user.login}`; + + if (title.startsWith('feat') || labels.includes('enhancement') || labels.includes('feature')) { + features.push(prLink); + } else if (title.startsWith('fix') || labels.includes('bug') || labels.includes('bugfix')) { + bugFixes.push(prLink); + } else { + other.push(prLink); + } + } + + // Build release notes + let notes = '## What\'s Changed\n\n'; + + if (features.length > 0) { + notes += '### โœจ Features\n' + features.join('\n') + '\n\n'; + } + + if (bugFixes.length > 0) { + notes += '### ๐Ÿ› Bug Fixes\n' + bugFixes.join('\n') + '\n\n'; + } + + if (other.length > 0) { + notes += '### ๐Ÿ”ง Other Changes\n' + other.join('\n') + '\n\n'; + } + + notes += `\n**Full Changelog**: https://github.com/${repo.owner}/${repo.repo}/compare/${lastReleaseDate.split('T')[0]}...${{ github.event.inputs.version }}`; + + return notes; + + - name: Create Release + uses: actions/github-script@v7 + env: + RELEASE_NOTES: ${{ steps.release_notes.outputs.result }} + with: + script: | + const fs = require('fs'); + const repo = context.repo; + + // Create the release + const { data: release } = await github.rest.repos.createRelease({ + owner: repo.owner, + repo: repo.repo, + tag_name: '${{ github.event.inputs.version }}', + name: '${{ github.event.inputs.version }}', + body: process.env.RELEASE_NOTES, + draft: false, + prerelease: ${{ github.event.inputs.prerelease }}, + target_commitish: context.sha + }); + + console.log(`Release created: ${release.html_url}`); + + // Upload the SourceGenerator DLL as a release asset + const dllPath = 'Arch.SystemGroups.SourceGenerator/bin/Release/netstandard2.0/Arch.SystemGroups.SourceGenerator.dll'; + const dllContent = fs.readFileSync(dllPath); + + await github.rest.repos.uploadReleaseAsset({ + owner: repo.owner, + repo: repo.repo, + release_id: release.id, + name: 'Arch.SystemGroups.SourceGenerator.dll', + data: dllContent, + }); + + console.log('SourceGenerator DLL uploaded successfully'); + + // Also create a summary + await core.summary + .addHeading('Release Created Successfully! ๐ŸŽ‰') + .addLink('View Release', release.html_url) + .addRaw('\n\n') + .addHeading('Release Details', 3) + .addList([ + `Version: ${{ github.event.inputs.version }}`, + `Pre-release: ${{ github.event.inputs.prerelease }}`, + `Assets: Arch.SystemGroups.SourceGenerator.dll` + ]) + .write(); diff --git a/.github/workflows/workflows.yaml b/.github/workflows/workflows.yaml index 08203d6..d73efe0 100644 --- a/.github/workflows/workflows.yaml +++ b/.github/workflows/workflows.yaml @@ -5,6 +5,8 @@ name: "Continuous Integration" on: push: + branches: + - main paths-ignore: - '**/*.md' - '**/*.gitignore' @@ -15,13 +17,12 @@ on: - '**/*.gitignore' - '**/*.gitattributes' workflow_dispatch: - branches: - - main - paths-ignore: - - '**/*.md' - - '**/*.gitignore' - - '**/*.gitattributes' - + +permissions: + contents: read + checks: write + pull-requests: write + jobs: build: name: Build Arch.SystemGroups @@ -40,10 +41,10 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 - + - uses: actions/checkout@v4 + - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x @@ -58,10 +59,21 @@ jobs: - name: Test run: dotnet test --logger trx --results-directory "TestResults" + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + TestResults/**/*.trx + check_name: '.NET Test Results' + comment_title: '๐Ÿงช Test Results' + report_individual_runs: true + deduplicate_classes_by_file_name: false + - name: Upload dotnet test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: dotnet-results + name: dotnet-results-${{ github.run_number }} path: TestResults # Use always() to always run this step to publish test results when there are test failures if: ${{ always() }} \ No newline at end of file diff --git a/Arch.SystemGroups.SourceGenerator/EdgesGenerator.cs b/Arch.SystemGroups.SourceGenerator/EdgesGenerator.cs index 17087c1..a9c9342 100644 --- a/Arch.SystemGroups.SourceGenerator/EdgesGenerator.cs +++ b/Arch.SystemGroups.SourceGenerator/EdgesGenerator.cs @@ -21,10 +21,18 @@ public static string GetValidateEdgesBody(IList updateBefore, IList var builder = new StringBuilder(); foreach (var dependency in updateBefore) + { + builder.AppendLine($"// [UpdateBefore({dependency})]"); + InsertValidateEdge(dependency); - + } + foreach (var dependency in updateAfter) + { + builder.AppendLine($"// [UpdateAfter({dependency})]"); + InsertValidateEdge(dependency); + } return builder.ToString(); @@ -51,6 +59,8 @@ public static StringBuilder GetAddEdgesBody(IList updateBefore, ILi // Filter out references to self if (typeSymbol.Equals(thisType, SymbolEqualityComparer.Default)) continue; + + builder.AppendLine($"// [UpdateAfter({typeSymbol})]"); // Update After = from That Type to This builder.AppendLine( @@ -62,6 +72,8 @@ public static StringBuilder GetAddEdgesBody(IList updateBefore, ILi if (typeSymbol.Equals(thisType, SymbolEqualityComparer.Default)) continue; + builder.AppendLine($"// [UpdateBefore({typeSymbol})]"); + // Update Before = from This Type to That builder.AppendLine( $"ArchSystemsSorter.AddEdge(typeof({className}{typeGenericArguments}), typeof({typeSymbol}), edgesMap);");