Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

Description

@ptr727

Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


A. #213 / #221 - version each leg from its checked-out branch, validate at entry

Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

A1. get-version-task.yml - make NBGV ignore the CI ref

On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

 - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
# Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

validate-release:
name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
- name: Validate branch and version consistency stepenv:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
  • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
  • github-release job: add validate-release to its needs:
  • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

B. #214 — surgical consume-then-delete, drop the blanket cleanup

B1. build-release-task.ymlgithub-release job — delete the consumed assets

After the Create GitHub release step, add:

Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

# Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
- name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

B2. publish-release.yml

  • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

    permissions:
    contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
  • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

B3. test-pull-request.yml

  • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

B4. Confirm the backstop

retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


C. #211 — rulesets: nothing to carry here

Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


Verify

  • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
  • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
  • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
  • CRLF preserved on every touched workflow file.

Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

      Description

      @ptr727

      Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

      Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

      This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


      A. #213 / #221 - version each leg from its checked-out branch, validate at entry

      Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

      A1. get-version-task.yml - make NBGV ignore the CI ref

      On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

       - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
      # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

      Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

      A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

      Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

      validate-release:
      name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
      - name: Validate branch and version consistency stepenv:
      SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
      • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
      • github-release job: add validate-release to its needs:
      • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

      B. #214 — surgical consume-then-delete, drop the blanket cleanup

      B1. build-release-task.ymlgithub-release job — delete the consumed assets

      After the Create GitHub release step, add:

      Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

      # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
      - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
      GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

      B2. publish-release.yml

      • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

        permissions:
        contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
      • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

      B3. test-pull-request.yml

      • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

      B4. Confirm the backstop

      retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


      C. #211 — rulesets: nothing to carry here

      Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


      Verify

      • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
      • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
      • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
      • CRLF preserved on every touched workflow file.

      Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

          Description

          @ptr727

          Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

          Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

          This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


          A. #213 / #221 - version each leg from its checked-out branch, validate at entry

          Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

          A1. get-version-task.yml - make NBGV ignore the CI ref

          On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

           - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
          # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

          Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

          A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

          Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

          validate-release:
          name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
          - name: Validate branch and version consistency stepenv:
          SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
          • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
          • github-release job: add validate-release to its needs:
          • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

          B. #214 — surgical consume-then-delete, drop the blanket cleanup

          B1. build-release-task.ymlgithub-release job — delete the consumed assets

          After the Create GitHub release step, add:

          Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

          # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
          - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
          GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

          B2. publish-release.yml

          • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

            permissions:
            contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
          • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

          B3. test-pull-request.yml

          • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

          B4. Confirm the backstop

          retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


          C. #211 — rulesets: nothing to carry here

          Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


          Verify

          • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
          • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
          • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
          • CRLF preserved on every touched workflow file.

          Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

              Description

              @ptr727

              Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

              Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

              This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


              A. #213 / #221 - version each leg from its checked-out branch, validate at entry

              Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

              A1. get-version-task.yml - make NBGV ignore the CI ref

              On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

               - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
              # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

              Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

              A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

              Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

              validate-release:
              name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
              - name: Validate branch and version consistency stepenv:
              SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
              • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
              • github-release job: add validate-release to its needs:
              • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

              B. #214 — surgical consume-then-delete, drop the blanket cleanup

              B1. build-release-task.ymlgithub-release job — delete the consumed assets

              After the Create GitHub release step, add:

              Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

              # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
              - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
              GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

              B2. publish-release.yml

              • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

                permissions:
                contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
              • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

              B3. test-pull-request.yml

              • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

              B4. Confirm the backstop

              retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


              C. #211 — rulesets: nothing to carry here

              Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


              Verify

              • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
              • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
              • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
              • CRLF preserved on every touched workflow file.

              Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

                  Description

                  @ptr727

                  Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

                  Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

                  This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


                  A. #213 / #221 - version each leg from its checked-out branch, validate at entry

                  Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

                  A1. get-version-task.yml - make NBGV ignore the CI ref

                  On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

                   - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
                  # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

                  Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

                  A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

                  Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

                  validate-release:
                  name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
                  - name: Validate branch and version consistency stepenv:
                  SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
                  • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
                  • github-release job: add validate-release to its needs:
                  • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

                  B. #214 — surgical consume-then-delete, drop the blanket cleanup

                  B1. build-release-task.ymlgithub-release job — delete the consumed assets

                  After the Create GitHub release step, add:

                  Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

                  # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
                  - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
                  GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

                  B2. publish-release.yml

                  • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

                    permissions:
                    contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
                  • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

                  B3. test-pull-request.yml

                  • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

                  B4. Confirm the backstop

                  retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


                  C. #211 — rulesets: nothing to carry here

                  Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


                  Verify

                  • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
                  • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
                  • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
                  • CRLF preserved on every touched workflow file.

                  Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

                      Description

                      @ptr727

                      Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

                      Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

                      This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


                      A. #213 / #221 - version each leg from its checked-out branch, validate at entry

                      Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

                      A1. get-version-task.yml - make NBGV ignore the CI ref

                      On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

                       - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
                      # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

                      Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

                      A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

                      Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

                      validate-release:
                      name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
                      - name: Validate branch and version consistency stepenv:
                      SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
                      • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
                      • github-release job: add validate-release to its needs:
                      • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

                      B. #214 — surgical consume-then-delete, drop the blanket cleanup

                      B1. build-release-task.ymlgithub-release job — delete the consumed assets

                      After the Create GitHub release step, add:

                      Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

                      # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
                      - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
                      GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

                      B2. publish-release.yml

                      • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

                        permissions:
                        contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
                      • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

                      B3. test-pull-request.yml

                      • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

                      B4. Confirm the backstop

                      retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


                      C. #211 — rulesets: nothing to carry here

                      Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


                      Verify

                      • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
                      • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
                      • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
                      • CRLF preserved on every touched workflow file.

                      Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

                          Description

                          @ptr727

                          Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

                          Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

                          This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


                          A. #213 / #221 - version each leg from its checked-out branch, validate at entry

                          Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

                          A1. get-version-task.yml - make NBGV ignore the CI ref

                          On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

                           - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
                          # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

                          Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

                          A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

                          Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

                          validate-release:
                          name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
                          - name: Validate branch and version consistency stepenv:
                          SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
                          • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
                          • github-release job: add validate-release to its needs:
                          • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

                          B. #214 — surgical consume-then-delete, drop the blanket cleanup

                          B1. build-release-task.ymlgithub-release job — delete the consumed assets

                          After the Create GitHub release step, add:

                          Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

                          # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
                          - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
                          GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

                          B2. publish-release.yml

                          • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

                            permissions:
                            contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
                          • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

                          B3. test-pull-request.yml

                          • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

                          B4. Confirm the backstop

                          retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


                          C. #211 — rulesets: nothing to carry here

                          Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


                          Verify

                          • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
                          • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
                          • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
                          • CRLF preserved on every touched workflow file.

                          Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Re-sync template #213/#214 workflow fixes (single-target adaptation) #195

                              Description

                              @ptr727

                              Re-sync of the two workflow fixes this repo surfaced upstream, now merged in ptr727/ProjectTemplate on develop:

                              Apply here adapted to this single-target NuGet repo (no pypi/docker/executable jobs). These workflow files are CRLF — preserve line endings. Source of truth for every snippet below is template PRs ptr727/ProjectTemplate#215, #216, and #218 (the #217 delete-gating follow-up).

                              This is the first downstream re-sync of these fixes; once validated here it goes to the other derived repos.


                              A. #213 / #221 - version each leg from its checked-out branch, validate at entry

                              Updated for #221 + #219. The original GITHUB_REF-override approach (template PR ptr727/ProjectTemplate#215) was ineffective - GITHUB_REF is GitHub-reserved and a step env: cannot override it (the runner re-injects the dispatch ref). Use NBGV's own IGNORE_GITHUB_REF instead (template PR ptr727/ProjectTemplate#222). If you already applied the earlier version of this section, see the migration note in the comments below.

                              A1. get-version-task.yml - make NBGV ignore the CI ref

                              On the Run Nerdbank.GitVersioning tool step, set NBGV's IGNORE_GITHUB_REF so it versions from the checked-out branch (each leg already checks out its own branch) instead of the dispatch ref:

                               - name: Run Nerdbank.GitVersioning tool stepid: nbgvuses: dotnet/nbgv@masterenv:
                              # Version from the checked-out branch, not the CI ref. GITHUB_REF is reserved and a step env can't reliably# override it; IGNORE_GITHUB_REF makes NBGV ignore it and use the checked-out branch. validate-release backstops.IGNORE_GITHUB_REF: "true"

                              Do not add a branch input to get-version-task.yml or thread branch into its callers - that was the ineffective #215 approach and is unnecessary.

                              A2. build-release-task.yml - validate-at-entry gate (smoke-skipped)

                              Add a validate-release job (right after get-version) that the build jobs needs:. It checks branch-versus-version consistency on real publishes and is skipped for smoke builds - a smoke build's detached PR HEAD always versions as prerelease, which would otherwise fail a main-base promotion PR (#219):

                              validate-release:
                              name: Validate release version jobneeds: [get-version]runs-on: ubuntu-lateststeps:
                              - name: Validate branch and version consistency stepenv:
                              SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}BRANCH: ${{ inputs.branch }}SMOKE: ${{ inputs.smoke }}run: | set -euo pipefail # Smoke builds never publish and always version as prerelease (detached PR HEAD), which would trip the main arm. if [[ "$SMOKE" == "true" ]]; then echo "Smoke build; skipping release version validation." exit 0 fi CORE_AND_PRE="${SEMVER2%%+*}" if [[ "$BRANCH" == "main" ]]; then if [[ "$CORE_AND_PRE" == *-* ]]; then echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish." exit 1 fi elif [[ "$CORE_AND_PRE" != *-* ]]; then echo "::error::Prerelease ($BRANCH) version '$SEMVER2' has no prerelease suffix (NBGV classified it public); refusing to publish." exit 1 fi
                              • build-nugetlibrary job: needs: [get-version] -> needs: [get-version, validate-release]
                              • github-release job: add validate-release to its needs:
                              • Remove the old Verify public release version step from github-release (the entry gate covers it both directions).

                              B. #214 — surgical consume-then-delete, drop the blanket cleanup

                              B1. build-release-task.ymlgithub-release job — delete the consumed assets

                              After the Create GitHub release step, add:

                              Includes the #217 refinement (template PR ptr727/ProjectTemplate#218): the delete is gated on the sameif: as the Create step (steps.release-exists is the existing "Check for existing release step" already in this job), so a scheduled re-run on an existing tag doesn't delete the freshly built artifacts. If you already applied an earlier un-gated version of this step, just update its if: line to match below.

                              # Surgical cleanup at the point of consumption: the release-asset-<branch>-* transfer artifacts now have durable# copies on the release, so delete them by exact pattern. Gated to the same condition as the Create step so it only# deletes when a release was actually created/refreshed this run; on a skipped create (existing tag, no new# commits) the fresh artifacts stay for the run, reaped by the retention-days: 1 backstop. Best-effort: a# listing/delete hiccup must not red the job. Deletes every matching id. Needs the caller `actions: write` (B2).
                              - name: Delete consumed release asset artifacts stepif: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}continue-on-error: trueenv:
                              GH_TOKEN: ${{ github.token }}run: | set -euo pipefail if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \ --jq ".artifacts[] | select(.name | startswith(\"release-asset-${{ inputs.branch }}-\")) | .id"); then echo "::warning::Could not list run artifacts; retention-days backstop will reap them." ids="" fi for id in $ids; do gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \ || echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it." done

                              B2. publish-release.yml

                              • On the publish job (the one that uses: ./.github/workflows/build-release-task.yml), add actions: write next to contents: write so the github-release job can delete:

                                permissions:
                                contents: write# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).actions: write
                              • Remove the cleanup-artifacts job entirely. (No publish-pypi here — single target — so no other consume-then-delete is needed.)

                              B3. test-pull-request.yml

                              • Remove the cleanup-artifacts job entirely. check-workflow-status does not list it in needs:, so the required merge check is unaffected. Smoke builds upload no release-asset-* (gated on !smoke), so there is nothing left to clean here.

                              B4. Confirm the backstop

                              retention-days: 1 must be set on the release-asset-<branch>-nugetlibrary upload in build-nugetlibrary-task.yml (it is the failure-path backstop for B1/B2).


                              C. #211 — rulesets: nothing to carry here

                              Branch rulesets are now maintained only in the template as .github/rulesets/{develop,main}.json (template PR ptr727/ProjectTemplate#212); they are not carried/re-synced as a per-repo file. This repo's live rulesets were already verified in sync with the template's committed JSON, so there is nothing to do. To re-confirm, run the drift check from the template's AGENTS.md "Staying in Sync" (from a template checkout, against ptr727/LanguageTags).


                              Verify

                              • actionlint clean: docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:latest
                              • validate-release fails a synthetic clean-X.Y.Z develop version and passes a X.Y.Z-g<sha> one; runs before the build job.
                              • After a real publish: no release-asset-* artifacts remain (consumed-then-deleted), diagnostics/build-records are retained; a forced mid-run failure leaves its artifact for retention-days: 1 rather than red-failing cleanup.
                              • CRLF preserved on every touched workflow file.

                              Once this is validated, the same set of changes (adapted per repo's target set) will be re-synced to the other derived repos.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions