-
Notifications
You must be signed in to change notification settings - Fork 0
ci: NuGet trusted-publishing workflow for 4 semconv packages #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,188 @@ | ||||||||||||||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||||||||||||||
| # NuGet Publish — OIDC Trusted Publishing | ||||||||||||||||||||||||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||
| # Publishes 4 packages to nuget.org via short-lived OIDC tokens (no long-lived | ||||||||||||||||||||||||||||||||||||||
| # NUGET_API_KEY secret): | ||||||||||||||||||||||||||||||||||||||
| # 1. Qyl.SemanticConventions (core types) | ||||||||||||||||||||||||||||||||||||||
| # 2. Qyl.OpenTelemetry.SemanticConventions (OTel stable attrs) | ||||||||||||||||||||||||||||||||||||||
| # 3. Qyl.OpenTelemetry.SemanticConventions.Incubating (OTel experimental) | ||||||||||||||||||||||||||||||||||||||
| # 4. Qyl.OpenTelemetry.SemanticConventions.Analyzers (Roslyn + codefixes) | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Trusted-publishing policies (already configured on nuget.org, per package): | ||||||||||||||||||||||||||||||||||||||
| # Package Owner = ANcpLua | ||||||||||||||||||||||||||||||||||||||
| # Repository Owner = Alexander-Nachtmann | ||||||||||||||||||||||||||||||||||||||
| # Repository = qyl | ||||||||||||||||||||||||||||||||||||||
| # Workflow File = nuget-publish.yml | ||||||||||||||||||||||||||||||||||||||
| # Environment = nuget | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Policies are in NuGet's 7-day "pending full activation" state (standard for | ||||||||||||||||||||||||||||||||||||||
| # private repos). First successful publish per package activates it permanently. | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Cross-branch reality (2026-04): | ||||||||||||||||||||||||||||||||||||||
| # Packages 1–3 live on branch `claude/focused-gauss-3c1f8d` (semconv PR). | ||||||||||||||||||||||||||||||||||||||
| # Package 4 lives on branch `claude/goofy-cohen-8f4c45` (analyzer PR). | ||||||||||||||||||||||||||||||||||||||
| # Until BOTH PRs merge to main, a tag push or workflow_dispatch on a feature | ||||||||||||||||||||||||||||||||||||||
| # branch will only publish the subset of packages present in that ref. The | ||||||||||||||||||||||||||||||||||||||
| # publish matrix is fail-fast:false by design — partial success is the | ||||||||||||||||||||||||||||||||||||||
| # expected outcome during this bootstrap window. Once both PRs merge, every | ||||||||||||||||||||||||||||||||||||||
| # tag push produces all 4 nupkgs. | ||||||||||||||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| name: NuGet Publish | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||||||||
| tags: | ||||||||||||||||||||||||||||||||||||||
| - 'v*' | ||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||
| inputs: | ||||||||||||||||||||||||||||||||||||||
| version: | ||||||||||||||||||||||||||||||||||||||
| description: 'Version to publish (without v prefix, e.g. 1.0.0)' | ||||||||||||||||||||||||||||||||||||||
| required: true | ||||||||||||||||||||||||||||||||||||||
| type: string | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normalize The manual path forwards Suggested fix - id: ver
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
REF_NAME: ${{ github.ref_name }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
- echo "version=${DISPATCH_VERSION}" >> "$GITHUB_OUTPUT"
+ VERSION="${DISPATCH_VERSION#v}"
+ test -n "$VERSION"
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "is_release=true" >> "$GITHUB_OUTPUT"
elif [[ "$GITHUB_REF" == refs/tags/v* ]]; thenAlso applies to: 73-75, 183-187 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||||||||||||||||||
| group: nuget-publish-${{ github.ref }} | ||||||||||||||||||||||||||||||||||||||
| cancel-in-progress: false | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 | ||||||||||||||||||||||||||||||||||||||
| DOTNET_NOLOGO: true | ||||||||||||||||||||||||||||||||||||||
| DOTNET_CLI_TELEMETRY_OPTOUT: 1 | ||||||||||||||||||||||||||||||||||||||
| CI: true | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||
| version: | ||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||
| outputs: | ||||||||||||||||||||||||||||||||||||||
| version: ${{ steps.ver.outputs.version }} | ||||||||||||||||||||||||||||||||||||||
| is_release: ${{ steps.ver.outputs.is_release }} | ||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v6 | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -nP '^\s*-\s*uses:\s*[^@]+@(?![0-9a-f]{40}\b)\S+' .github/workflows/nuget-publish.ymlRepository: Alexander-Nachtmann/qyl Length of output: 299 Pin all GitHub Actions to immutable commit SHAs. Every
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||
| - id: ver | ||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||
| EVENT_NAME: ${{ github.event_name }} | ||||||||||||||||||||||||||||||||||||||
| DISPATCH_VERSION: ${{ github.event.inputs.version }} | ||||||||||||||||||||||||||||||||||||||
| REF_NAME: ${{ github.ref_name }} | ||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | ||||||||||||||||||||||||||||||||||||||
| echo "version=${DISPATCH_VERSION}" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||
| echo "is_release=true" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||
| elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then | ||||||||||||||||||||||||||||||||||||||
| echo "version=${REF_NAME#v}" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||
| echo "is_release=true" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+78
|
||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||
| COMMITS=$(git rev-list --count HEAD) | ||||||||||||||||||||||||||||||||||||||
| SHA=$(git rev-parse --short HEAD) | ||||||||||||||||||||||||||||||||||||||
| echo "version=0.0.${COMMITS}-ci.g${SHA}" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||
| echo "is_release=false" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Compile on all three OSes to catch platform-specific issues before we push. | ||||||||||||||||||||||||||||||||||||||
| # Scoped to the 4 publishable csprojs — qyl.slnx contains unrelated projects | ||||||||||||||||||||||||||||||||||||||
| # (collector, dashboard, loom) with WIP CI state that isn't our concern here. | ||||||||||||||||||||||||||||||||||||||
| build: | ||||||||||||||||||||||||||||||||||||||
| needs: version | ||||||||||||||||||||||||||||||||||||||
| strategy: | ||||||||||||||||||||||||||||||||||||||
| fail-fast: false | ||||||||||||||||||||||||||||||||||||||
| matrix: | ||||||||||||||||||||||||||||||||||||||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||||||||||||||||||||||||||||||||||||||
| runs-on: ${{ matrix.os }} | ||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v6 | ||||||||||||||||||||||||||||||||||||||
| - uses: actions/setup-dotnet@v5 | ||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| global-json-file: global.json | ||||||||||||||||||||||||||||||||||||||
| - name: Build Qyl.SemanticConventions | ||||||||||||||||||||||||||||||||||||||
| run: dotnet build packages/Qyl.SemanticConventions/Qyl.SemanticConventions.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | ||||||||||||||||||||||||||||||||||||||
| - name: Build Qyl.OpenTelemetry.SemanticConventions | ||||||||||||||||||||||||||||||||||||||
| run: dotnet build packages/Qyl.OpenTelemetry.SemanticConventions/Qyl.OpenTelemetry.SemanticConventions.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | ||||||||||||||||||||||||||||||||||||||
| - name: Build Qyl.OpenTelemetry.SemanticConventions.Incubating | ||||||||||||||||||||||||||||||||||||||
| run: dotnet build packages/Qyl.OpenTelemetry.SemanticConventions.Incubating/Qyl.OpenTelemetry.SemanticConventions.Incubating.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | ||||||||||||||||||||||||||||||||||||||
| - name: Build Qyl.OpenTelemetry.SemanticConventions.Analyzers | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+101
to
+107
|
||||||||||||||||||||||||||||||||||||||
| - name: Build Qyl.SemanticConventions | |
| run: dotnet build packages/Qyl.SemanticConventions/Qyl.SemanticConventions.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | |
| - name: Build Qyl.OpenTelemetry.SemanticConventions | |
| run: dotnet build packages/Qyl.OpenTelemetry.SemanticConventions/Qyl.OpenTelemetry.SemanticConventions.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | |
| - name: Build Qyl.OpenTelemetry.SemanticConventions.Incubating | |
| run: dotnet build packages/Qyl.OpenTelemetry.SemanticConventions.Incubating/Qyl.OpenTelemetry.SemanticConventions.Incubating.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | |
| - name: Build Qyl.OpenTelemetry.SemanticConventions.Analyzers | |
| - name: Build Qyl.SemanticConventions | |
| if: ${{ hashFiles('packages/Qyl.SemanticConventions/Qyl.SemanticConventions.csproj') != '' }} | |
| run: dotnet build packages/Qyl.SemanticConventions/Qyl.SemanticConventions.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | |
| - name: Build Qyl.OpenTelemetry.SemanticConventions | |
| if: ${{ hashFiles('packages/Qyl.OpenTelemetry.SemanticConventions/Qyl.OpenTelemetry.SemanticConventions.csproj') != '' }} | |
| run: dotnet build packages/Qyl.OpenTelemetry.SemanticConventions/Qyl.OpenTelemetry.SemanticConventions.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | |
| - name: Build Qyl.OpenTelemetry.SemanticConventions.Incubating | |
| if: ${{ hashFiles('packages/Qyl.OpenTelemetry.SemanticConventions.Incubating/Qyl.OpenTelemetry.SemanticConventions.Incubating.csproj') != '' }} | |
| run: dotnet build packages/Qyl.OpenTelemetry.SemanticConventions.Incubating/Qyl.OpenTelemetry.SemanticConventions.Incubating.csproj -c Release -p:Version=${{ needs.version.outputs.version }} | |
| - name: Build Qyl.OpenTelemetry.SemanticConventions.Analyzers | |
| if: ${{ hashFiles('packages/Qyl.OpenTelemetry.SemanticConventions.Analyzers/Qyl.OpenTelemetry.SemanticConventions.Analyzers.csproj') != '' }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bootstrap strategy is blocked by the unconditional four-project build.
Lines 101-108 build all four .csproj files before publish starts. On the branch states described in Lines 21-28, at least one of those paths is absent, so build fails and no package reaches the matrix publish step. Reuse the package matrix for build, or guard each build with a file-existence check.
As per coding guidelines, .github/**: GitHub Actions workflows. Review for: job dependency correctness.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/nuget-publish.yml around lines 86 - 108, The build job
currently unconditionally runs dotnet build for four projects
(packages/Qyl.SemanticConventions/Qyl.SemanticConventions.csproj,
packages/Qyl.OpenTelemetry.SemanticConventions/Qyl.OpenTelemetry.SemanticConventions.csproj,
packages/Qyl.OpenTelemetry.SemanticConventions.Incubating/Qyl.OpenTelemetry.SemanticConventions.Incubating.csproj,
packages/Qyl.OpenTelemetry.SemanticConventions.Analyzers/Qyl.OpenTelemetry.SemanticConventions.Analyzers.csproj)
which causes failure when one or more paths are missing; modify the workflow so
the build step either reuses the existing package matrix (matrix-driven job) or
wraps each dotnet build invocation in a file-existence check (e.g., test for the
.csproj path before running dotnet build), ensuring the build job succeeds and
allows the publish matrix job to run.
Copilot
AI
Apr 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments say this release job runs “after every publish leg finishes (success or partial)”, but needs: [version, publish] means it only runs if the overall publish job succeeds. With a matrix, a single failing leg makes publish fail, so this job will be skipped and you won’t get a GitHub release on partial bootstrap publishes. If you want the release job to run after the matrix completes regardless of failures, use an if: always() && needs.version.outputs.is_release == 'true' condition (and optionally gate creation based on needs.publish.result).
| if: needs.version.outputs.is_release == 'true' | |
| if: always() && needs.version.outputs.is_release == 'true' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial publish currently suppresses release creation.
fail-fast: false only keeps the matrix running; it does not make publish succeed. If any publish leg fails, needs: publish causes release to be skipped, so the workflow cannot deliver the “partial success during bootstrap” behavior documented above. If partial publish is intentional, move release behind an explicit fan-in that uses always() plus a computed success signal.
As per coding guidelines, .github/**: GitHub Actions workflows. Review for: job dependency correctness.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/nuget-publish.yml around lines 167 - 172, The release job
is skipped if any matrix publish leg fails because "needs: publish" requires all
legs to succeed; create an explicit fan-in aggregator job (e.g.,
"publish-fan-in") that has needs: publish and runs regardless of leg outcomes
(use if: always() or run with a step that uses job.status), compute a boolean
output like publish-fan-in.outputs.partial_or_full_success (set via a step that
checks needs.publish[*].result or job.status to detect any_success or
all_failed), and then change the release job to depend on publish-fan-in
(replace needs: publish with needs: [version, publish-fan-in]) and use the
aggregator output in the release if condition instead of relying on
needs.publish; keep the existing version check
(needs.version.outputs.is_release) combined with the aggregator output so
release runs for intended partial publishes while preserving fail-fast: false on
the matrix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The header comment hard-codes temporary branch names and a point-in-time note (“Cross-branch reality (2026-04) …”). This will become stale immediately after merge and can mislead future maintainers. Consider replacing it with a durable reference to the prerequisite PRs/packages (or removing the branch-specific details entirely).