Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-nugetlibrary-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

build-nugetlibrary:
name: Build NuGet library project job
Expand All@@ -46,7 +47,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand Down
71 changes: 54 additions & 17 deletions .github/workflows/build-release-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,11 +50,38 @@ jobs:
secrets: inherit
with:
ref: ${{ inputs.ref }}
branch: ${{ inputs.branch }}

# Entry gate: validate branch<->version consistency once, before the build jobs, so an NBGV mis-classification fails
# fast instead of after building and publishing. main must be a public release (no prerelease '-'); every other branch
# must carry a prerelease '-' (guards a develop leg being classified public and published as stable). Strip
# '+buildmetadata' first; a '-' there is legitimate, only a '-' in the core/prerelease segment marks a prerelease.
validate-release:
name: Validate release version job
needs: [get-version]
runs-on: ubuntu-latest
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
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:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
needs: [get-version, validate-release]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
Expand All@@ -72,7 +99,7 @@ jobs:
# `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-nugetlibrary]
needs: [get-version, validate-release, build-nugetlibrary]

steps:

Expand All@@ -82,21 +109,6 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
Expand DownExpand Up@@ -150,3 +162,28 @@ jobs:
LICENSE
README.md
./Publish/*

# 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 to free the storage quota - scoped to this branch's
# assets, leaving diagnostics and any other artifacts. 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. Needs the caller to
# grant `actions: write` (publish-release's publish job does).
- name: Delete consumed release asset artifacts step
if: ${{ inputs.expect_release_assets && (steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch') }}
# Best-effort: the release is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
env:
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
13 changes: 12 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,12 @@ on:
required: false
type: string
default: ''
# Logical branch NBGV classifies against. Pins PublicRelease to this branch instead of the runner's GITHUB_REF,
# which on a publish dispatched from the default branch is that branch for every matrix leg. Empty keeps GITHUB_REF.
branch:
required: false
type: string
default: ''
outputs:
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
Expand DownExpand Up@@ -37,7 +43,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand All@@ -52,3 +58,8 @@ jobs:
- name: Run Nerdbank.GitVersioning tool step
id: nbgv
uses: dotnet/nbgv@master
env:
# NBGV reads the branch from GITHUB_REF; pin it to the leg being versioned so a publish dispatched from the
# default branch doesn't classify every leg as the public ref and strip its prerelease suffix.
GITHUB_REF: ${{ inputs.branch != '' && format('refs/heads/{0}', inputs.branch) || github.ref }}
GITHUB_REF_NAME: ${{ inputs.branch != '' && inputs.branch || github.ref_name }}
28 changes: 2 additions & 26 deletions .github/workflows/publish-release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,6 +78,8 @@ jobs:
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
Expand All@@ -96,29 +98,3 @@ jobs:
secrets: inherit
permissions:
contents: write

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [setup, publish, date-badge]
if: ${{ always() && needs.setup.outputs.publish == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done
29 changes: 1 addition & 28 deletions .github/workflows/test-pull-request.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ jobs:
steps:

- name: Setup .NET SDK step
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.x

Expand DownExpand Up@@ -115,30 +115,3 @@ jobs:
# smoke-build may be legitimately skipped (library unchanged); only failure/cancelled blocks.
exit_on_result "unit-test" "${{ needs.unit-test.result }}"
exit_on_result "smoke-build" "${{ needs.smoke-build.result }}"

# Delete any incidental artifacts a build step emitted to keep them off the account storage quota. Kept out of
# `check-workflow-status`'s needs so housekeeping never gates the required merge check.
cleanup-artifacts:
name: Delete workflow artifacts job
needs: [smoke-build]
if: always()
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow artifacts step
# Best-effort housekeeping must never fail the run.
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ids=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[].id'); then
echo "::warning::Could not list run artifacts; skipping cleanup."
ids=""
fi
for artifact_id in $ids; do
gh api --method DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" \
|| echo "::warning::Failed to delete artifact $artifact_id; continuing."
done