Skip to content

SQLBI Whiteboard 0.9.5 #8

SQLBI Whiteboard 0.9.5

SQLBI Whiteboard 0.9.5 #8

Workflow file for this run

# Submits a released version to the Windows Package Manager repository.
#
# winget reaches the audience that never visits a landing page, and unlike the Microsoft
# Store it is normally available on managed corporate machines, where Store access is
# uneven (decision 10).
#
# This runs beside the release rather than inside it, for the same reason the Store
# submission does (decision 13): a submission is a pull request against
# microsoft/winget-pkgs, it is reviewed by people, and it can sit for days. Nothing about
# the download being available may depend on it. A failure here is visible as a failed run
# and changes nothing that has already shipped.
#
# Setup was done on 20 August 2026 and is recorded here because it has to be repeated if
# the token is ever rotated or the account changes.
#
# 1. The first submission was made by hand. `wingetcreate update` below reads the
# previous version's manifests out of winget-pkgs, so it cannot make the first one.
# From a Windows machine with this repository checked out:
#
# winget install Microsoft.WingetCreate
# winget validate installer\winget
# wingetcreate submit installer\winget
#
# With no --token, wingetcreate runs a browser OAuth flow, which is what the tool's
# own documentation recommends locally: a token passed on the command line ends up in
# shell history and in any transcript of the session.
#
# 2. WINGET_TOKEN is a repository secret holding a GitHub personal access token. A
# workflow has no browser, so this is the one place a token is unavoidable.
#
# It must be a **classic** token - wingetcreate does not support fine-grained tokens,
# which is now what GitHub offers first. The scope is public_repo, not full repo;
# winget-pkgs and the fork are both public. delete_repo is worth adding as well: it
# lets wingetcreate remove the fork it created when a submission fails, instead of
# leaving one behind on every failure.
#
# wingetcreate forks winget-pkgs and opens the pull request as whoever owns the
# token, so it is an identity rather than only a permission. GITHUB_TOKEN cannot
# stand in: it has no rights outside this repository.
name: Publish to winget
on:
release:
types: [published]
# Re-submitting a release whose first attempt failed, or catching one published before
# this workflow existed, without cutting a new release to do it.
workflow_dispatch:
inputs:
tag:
description: 'Release tag to submit (default: the latest released version)'
required: false
type: string
permissions:
contents: read
concurrency:
group: winget
cancel-in-progress: false
jobs:
submit:
name: Submit to winget-pkgs
# Pre-releases are the Dev channel, which installs as a separate product so a tester
# can run it beside a released copy. Only the released channel belongs in a package
# manager. github.event.release is absent on workflow_dispatch, so the guard has to
# allow that case through explicitly.
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- name: Resolve the release
id: release
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
EVENT_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: |
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$tag = if ($env:INPUT_TAG) { $env:INPUT_TAG } elseif ($env:EVENT_TAG) { $env:EVENT_TAG } else { '' }
if ($tag) {
$release = gh release view $tag --json tagName,isPrerelease,assets | ConvertFrom-Json
}
else {
# --exclude-pre-releases is what makes "latest" mean the released channel.
$tag = (gh release list --limit 1 --exclude-pre-releases --json tagName |
ConvertFrom-Json).tagName
if (-not $tag) { throw 'No released version has been published yet.' }
$release = gh release view $tag --json tagName,isPrerelease,assets | ConvertFrom-Json
}
if ($release.isPrerelease) {
throw "$tag is a pre-release. Only the released channel is submitted to winget."
}
$version = $release.tagName -replace '^v', ''
if ($version -notmatch '^\d+\.\d+\.\d+$') {
throw "Expected a major.minor.patch tag, but '$($release.tagName)' gives '$version'."
}
# The two installers winget offers, machine scope first so the order matches the
# manifest. Anything framework-dependent is a pipeline artifact, not a download.
$perMachine = $release.assets |
Where-Object { $_.name -like '*.msi' -and $_.name -notlike '*userinstaller*' -and $_.name -notlike '*frameworkdependent*' -and $_.name -notlike '*-dev*' } |
Select-Object -First 1
$perUser = $release.assets |
Where-Object { $_.name -like '*userinstaller.msi' -and $_.name -notlike '*frameworkdependent*' -and $_.name -notlike '*-dev*' } |
Select-Object -First 1
if (-not $perMachine) { throw "$tag has no per-machine installer." }
if (-not $perUser) { throw "$tag has no per-user installer." }
# wingetcreate reads metadata appended to a URL as <url>|<architecture>|<scope>.
# Both are given rather than letting it infer: with one trailing field it has to
# guess which of the two was meant, and the two installers here differ only by
# scope.
$base = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/$tag"
"version=$version" >> $env:GITHUB_OUTPUT
"tag=$tag" >> $env:GITHUB_OUTPUT
"urls=$base/$($perMachine.name)|x64|machine $base/$($perUser.name)|x64|user" >> $env:GITHUB_OUTPUT
Write-Host "Submitting $version from $tag"
Write-Host " $($perMachine.name)"
Write-Host " $($perUser.name)"
- name: Check the submission token is present
shell: pwsh
env:
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
run: |
if (-not $env:WINGET_TOKEN) {
throw 'WINGET_TOKEN is not set. See the setup notes at the top of this workflow.'
}
# wingetcreate downloads each installer, computes its own SHA256, and reads the
# ProductCode out of the MSI. The hashes in installer/winget/ are the seed for the
# first submission only; from here they are derived from the bytes being submitted,
# so a mismatch is impossible rather than merely unlikely.
- name: Submit the update
shell: pwsh
env:
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
VERSION: ${{ steps.release.outputs.version }}
URLS: ${{ steps.release.outputs.urls }}
run: |
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$exe = Join-Path $env:RUNNER_TEMP 'wingetcreate.exe'
Invoke-WebRequest -Uri 'https://aka.ms/wingetcreate/latest' -OutFile $exe
# $urls, not @urls: the @ prefix is splatting, which applies to cmdlets. A plain
# array passed to a native command already expands to one argument per element.
$urls = $env:URLS -split ' '
& $exe update SQLBI.Whiteboard `
--version $env:VERSION `
--urls $urls `
--submit `
--token $env:WINGET_TOKEN
if ($LASTEXITCODE -ne 0) {
throw "wingetcreate exited with $LASTEXITCODE. If this is the first submission, installer/winget/ has to be submitted by hand once before update can work."
}