Compare commits

..
7 Commits
Author SHA1 Message Date
koftwentytwo-standards-releases[bot] 0ef3d319b8 chore(main): release 1.2.0 (#48)
Release proposal generated from reviewed changes on main. Review the
version, changes, and validation before merging.
---


## 1.2.0 (2026-10-13)

<!-- Release notes generated using configuration in .github/release.yml
at main -->

## What's Changed
### Changes
* feat(releases): publish signed per-user MSI installers by
@KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/47


**Full Changelog**:
https://github.com/KofTwentyTwo/standards/compare/v1.1.2...v1.2.0

---
Generated by [Release
Please](https://github.com/googleapis/release-please) using this
repository's release configuration.

Co-authored-by: koftwentytwo-standards-releases[bot] <340613191+koftwentytwo-standards-releases[bot]@users.noreply.github.com>
2026-10-13 08:10:06 -05:00
james.maesandCodex GPT-6 7584fa19cc feat(releases): publish signed per-user MSI installers (#47)
Provide optional per-user MSI alongside existing EXE/ZIP assets using
pinned Velopack1.2.161. Keep the existing signing hook, preserve
third-party signatures, verify the administratively extracted MSI
payload through the existing signed-archive verifier, require native MSI
install/window launch/uninstall before publication, and include MSI in
SBOM subjects/checksums/provenance. The default remains EXE-only for
existing callers.

Validation: actionlint and configured zizmor medium gate pass; all40
signing regressions pass with zero skips. A real native MSI fixture
compiles and the actual GitHub-wrapped extraction body preserves all6
signed input application binaries byte-for-byte. Native MSI ProductCode
lookup succeeds. The first local administrative target exceeded Windows
Installer's path limit; a short normal temporary root succeeds, matching
hosted runner paths. This fixture is unsigned and not published;
production MSI signing/installation is required before claiming
delivery.

The maintainer explicitly renewed EXE+MSI delivery and instructed
completion without further human-review requests. Existing publication
authorization applies. No Azure identity, scope or credential changes.
Substantial AI assistance: Codex GPT-6. Refs
KofTwentyTwo/MarkdownViewr.Windows#36 and #33.

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Codex GPT-6 <noreply@openai.com>
2026-10-13 08:05:38 -05:00
koftwentytwo-standards-releases[bot] 083d90326a chore(main): release 1.1.2 (#46)
Release proposal generated from reviewed changes on main. Review the
version, changes, and validation before merging.
---


## 1.1.2 (2026-10-13)

<!-- Release notes generated using configuration in .github/release.yml
at main -->

## What's Changed
### Changes
* fix(releases): scope bootstrap parameters under Actions pwsh by
@KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/45


**Full Changelog**:
https://github.com/KofTwentyTwo/standards/compare/v1.1.1...v1.1.2

---
Generated by [Release
Please](https://github.com/googleapis/release-please) using this
repository's release configuration.

Co-authored-by: koftwentytwo-standards-releases[bot] <340613191+koftwentytwo-standards-releases[bot]@users.noreply.github.com>
2026-10-13 06:10:59 -05:00
james.maesandCodex GPT-6 261a0055ae fix(releases): scope bootstrap parameters under Actions pwsh (#45)
Both1.0.1 production runs reached the fixed builder after full
acceptance. The inline dependency bootstrap failed with `param is not
recognized` because GitHub's default pwsh shell inserts
`$ErrorActionPreference = 'stop'` before the run body. The previous
standalone-script test did not model that wrapper; this was my
implementation error.

Invoke the existing parameterized bootstrap inside a script block and
pass its arguments through. Preserve every package
URL/version/hash/signature check, destination guard, official action
input and Azure trust scope. Update tests to execute the actual run body
after GitHub's preamble.

Validation: added the preamble to the existing real-body test first and
reproduced all five bootstrap cases failing with the exact hosted param
error in2.26sec; after the fix40 signing tests pass with zero skips. The
actual corrected wrapped body also downloads/verifies Microsoft's real
pinned package and starts its CLI. Configured script analysis,
actionlint, zizmor medium gate and diff checks pass. No production
signing/publication success claimed.

Refs #43 and KofTwentyTwo/MarkdownViewr.Windows#33. Substantial AI
assistance: Codex GPT-6. Required genuine maintainer review before
merge; app release authorization already exists.

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Codex GPT-6 <noreply@openai.com>
2026-10-13 06:08:28 -05:00
koftwentytwo-standards-releases[bot] 96cbdf208a chore(main): release 1.1.1 (#42)
Release proposal generated from reviewed changes on main. Review the
version, changes, and validation before merging.
---


## 1.1.1 (2026-10-12)

<!-- Release notes generated using configuration in .github/release.yml
at main -->

## What's Changed
### Changes
* docs(inventory): register MarkdownViewr and the Windows starter by
@KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/36
* fix(releases): bootstrap verified Artifact Signing dependency by
@KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/44


**Full Changelog**:
https://github.com/KofTwentyTwo/standards/compare/v1.1.0...v1.1.1

---
Generated by [Release
Please](https://github.com/googleapis/release-please) using this
repository's release configuration.

Co-authored-by: koftwentytwo-standards-releases[bot] <340613191+koftwentytwo-standards-releases[bot]@users.noreply.github.com>
2026-10-12 18:24:28 -05:00
james.maesandCodex GPT-6 b3e4b4989c fix(releases): bootstrap verified Artifact Signing dependency (#44)
The MarkdownViewr dev release reaches Azure OIDC successfully but the
official Artifact Signing action fails bootstrapping its pinned Sign
CLI: `--add-source` is incompatible with the caller's NuGet package
source mapping.

Prestage the exact action dependency from nuget.org with a fixed SHA256
and `dotnet nuget verify --all`. The official action then finds the
genuine package in its normal dependency directory. Caller NuGet policy,
OIDC trust, signing inputs, ownership catalog and final
signature/distributed-byte gates stay intact. Existing dependency
directories are preserved and rejected rather than replaced.

Fixes #43.

Validation: reproduced the hosted error locally; executed the actual new
workflow step with Microsoft's real signed package and launched its CLI;
all 40 signing regression tests pass (zero skips); full configured
PowerShell analysis, actionlint and zizmor pass. Five added tests cover
valid staging, changed download, invalid signature, existing directory
and missing executable. No production signing or publication claimed
yet.

Substantial AI assistance: implemented with Codex GPT-6. Maintainer
review required by K22-AI-01 before merge.

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Codex GPT-6 <noreply@openai.com>
2026-10-12 18:22:02 -05:00
james.maesandCodex 089a94ac3c docs: register MarkdownViewr and Windows starter as Products
MarkdownViewr.Windows and windows-app-template are public Windows
products but are absent from the required repository inventory. Add both
as Product tier, with adoption-in-progress status and their actual
purpose.

This resolves the missing inventory proposal for K22-REPO-42 without
asserting conformance, signed release readiness or completed maintainer
review. Existing people, access, repository rows and policies remain
unchanged. Inventory acceptance requires this PR to be reviewed and
merged.

Validation: markdownlint-cli2 v0.23.3 reports zero issues for
MAINTAINERS.md; git diff --check passes; commit is signed (G) and DCO
signed off. The new links point to the existing public repositories.
Required hosted documentation/conformance checks and maintainer review
remain pending.

Related release tracking:
https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/10. Shared
defaults: AppKit PR#29 and windows-app-template PR#4. Substantial AI
assistance: Codex (GPT-6).

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Codex (GPT-6) <noreply@openai.com>
2026-10-12 11:59:29 -05:00
6 changed files with 256 additions and 3 deletions

No files matched your search

+147 -1
View File
@@ -108,6 +108,10 @@ on:
description: Newline-separated required owned file names or publish-relative patterns, including the optional CLI; required when sign is true.
type: string
default: ""
msi:
description: Generate a per-user MSI alongside Setup.exe and portable ZIP; requires the same signing and verification.
type: boolean
default: false
outputs:
version:
description: The released version (tag without the leading v).
@@ -366,6 +370,37 @@ jobs:
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
# The pinned official action installs this tool with --add-source, which
# conflicts with NuGet package source mapping. Stage its verified package
# in the action's own dependency directory without changing caller policy.
- name: Prepare pinned Artifact Signing CLI dependency
if: inputs.sign
shell: pwsh
run: |
& {
param(
[string]$PackageDirectory = $env:RUNNER_TEMP,
[string]$InstallRoot = $env:LOCALAPPDATA
)
$ErrorActionPreference = 'Stop'
$version = '0.9.1-beta.26227.3'
$package = Join-Path $PackageDirectory "sign.$version.nupkg"
$destination = Join-Path $InstallRoot "ArtifactSigning/sign/sign.$version"
if (Test-Path -LiteralPath $destination) { throw 'Artifact Signing CLI directory already exists; use a fresh release runner.' }
Invoke-WebRequest -Uri "https://api.nuget.org/v3-flatcontainer/sign/$version/sign.$version.nupkg" -OutFile $package
if ((Get-FileHash -LiteralPath $package -Algorithm SHA256).Hash -ne '34FD0D4AEABDBC363A48883881865B4DD65E6C11BA916028082FA23F3E1B1BA1')
{
throw 'Artifact Signing CLI package checksum mismatch.'
}
dotnet nuget verify $package --all
if ($LASTEXITCODE -ne 0) { throw 'Artifact Signing CLI NuGet signature verification failed.' }
[IO.Compression.ZipFile]::ExtractToDirectory($package, $destination)
if (-not (Test-Path -LiteralPath (Join-Path $destination 'tools/net8.0/any/sign.dll') -PathType Leaf))
{
throw 'Artifact Signing CLI package is missing its executable.'
}
} @args
# Signs the app's own executables and DLLs (and the CLI) before packing, so the
# binaries inside the installer, the portable zip, and the update packages are
# all signed (K22-REL-05).
@@ -463,6 +498,8 @@ jobs:
- name: Velopack pack
shell: pwsh
env:
VPK_MSI: ${{ inputs.msi }}
run: |
$sign = @()
if ($env:K22_SIGN -eq 'true')
@@ -474,7 +511,7 @@ jobs:
$sign = @('--signTemplate', $command, '--signExclude', $exclude)
}
$env:VELOPACK_TEMP = Join-Path $env:RUNNER_TEMP 'k22-velopack'
vpk pack --runtime $env:K22_RUNTIME --packId $env:K22_APP_ID --packVersion $env:K22_VERSION --packDir publish/app --mainExe $env:K22_MAIN_EXE --channel $env:K22_CHANNEL --icon $env:K22_ICON --outputDir Releases @sign
vpk pack --runtime $env:K22_RUNTIME --packId $env:K22_APP_ID --packVersion $env:K22_VERSION --packDir publish/app --mainExe $env:K22_MAIN_EXE --channel $env:K22_CHANNEL --icon $env:K22_ICON --outputDir Releases --instLocation PerUser @sign
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# Keeps only what this build produced, as `vpk upload` does: the files listed in
@@ -503,6 +540,108 @@ jobs:
Write-Output "Assets: $($names -join ', '); feed: $feedName; setup: $setup"
"setup=$setup" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Extract required MSI payload
if: inputs.msi
id: msi
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$installers = @(Get-ChildItem -LiteralPath release -Filter '*.msi' -File)
if ($installers.Count -ne 1) { throw 'Exactly one MSI installer is required.' }
$root = Join-Path $env:RUNNER_TEMP ('k22-msi-' + [guid]::NewGuid().ToString('N'))
"root=$root" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
$image = Join-Path $root 'image'
$assets = Join-Path $root 'assets'
New-Item -ItemType Directory -Path $image, $assets -Force | Out-Null
$start = [Diagnostics.ProcessStartInfo]::new('msiexec.exe')
$start.UseShellExecute = $false
$start.CreateNoWindow = $true
foreach ($argument in @('/a', $installers[0].FullName, '/qn', "TARGETDIR=$image", '/l*v', (Join-Path $root 'extract.log')))
{
$start.ArgumentList.Add($argument)
}
$process = [Diagnostics.Process]::Start($start)
try
{
if (-not $process.WaitForExit(180000)) { $process.Kill(); throw 'MSI extraction timed out.' }
if ($process.ExitCode -ne 0) { throw "MSI extraction failed: $($process.ExitCode)." }
}
finally { $process.Dispose() }
$manifests = @(Get-ChildItem -LiteralPath $image -Recurse -File -Filter sq.version)
if ($manifests.Count -ne 1 -or $manifests[0].Directory.Name -ne 'current') { throw 'Expected one installed MSI application payload.' }
$payload = $manifests[0].Directory.Parent.FullName
[IO.Compression.ZipFile]::CreateFromDirectory($payload, (Join-Path $assets 'msi-Portable.zip'))
"directory=$assets" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
# Reuse the existing archive verifier for MSI's administratively extracted
# payload: every owned/third-party byte and generated launcher is checked.
- name: Verify exact signed MSI payload
if: inputs.msi && inputs.sign
uses: KofTwentyTwo/standards/.github/actions/windows-signing@f896215ec96854b18883191759259fd9866ee9be # reviewed signing helpers on main
with:
operation: verify-distribution
directory: ${{ steps.msi.outputs.directory }}
manifest: ${{ steps.signing.outputs.manifest }}
expected-subject: ${{ vars.ARTIFACT_SIGNING_SUBJECT || vars.TRUSTED_SIGNING_SUBJECT }}
- name: Verify MSI installs, launches and uninstalls
if: inputs.msi
shell: pwsh
env:
MSI_ROOT: ${{ steps.msi.outputs.root }}
run: |
$ErrorActionPreference = 'Stop'
$msi = @(Get-ChildItem -LiteralPath release -Filter '*.msi' -File)
if ($msi.Count -ne 1) { throw 'Exactly one MSI installer is required.' }
$installer = New-Object -ComObject WindowsInstaller.Installer
$database = $installer.OpenDatabase($msi[0].FullName, 0)
$view = $database.OpenView('SELECT `Value` FROM `Property` WHERE `Property` = ''ProductCode''')
$view.Execute()
$product = $view.Fetch().StringData(1)
if ($product -notmatch '^\{[0-9A-Fa-f-]{36}\}$') { throw 'Invalid MSI product identity.' }
$target = Join-Path $env:MSI_ROOT 'installed'
$application = $null
try
{
$install = Start-Process msiexec.exe -ArgumentList @('/i', ('"' + $msi[0].FullName + '"'), '/qn', ('VELOPACK_INSTALLDIR="' + $target + '"'), '/l*v', ('"' + (Join-Path $env:MSI_ROOT 'install.log') + '"')) -WindowStyle Hidden -PassThru
if (-not $install.WaitForExit(180000)) { $install.Kill(); throw 'MSI installation timed out.' }
if ($install.ExitCode -ne 0) { throw "MSI installation failed: $($install.ExitCode)." }
$executable = Join-Path $target "current/$env:K22_MAIN_EXE"
if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) { throw 'MSI did not install the application.' }
$application = Start-Process -FilePath $executable -PassThru
$deadline = [DateTime]::UtcNow.AddSeconds(30)
do
{
$application.Refresh()
if ($application.HasExited) { throw 'MSI-installed application exited before opening its window.' }
if ($application.MainWindowHandle -ne [IntPtr]::Zero) { break }
Start-Sleep -Milliseconds 200
} while ([DateTime]::UtcNow -lt $deadline)
if ($application.MainWindowHandle -eq [IntPtr]::Zero) { throw 'MSI-installed application did not open a window.' }
if (-not $application.CloseMainWindow() -or -not $application.WaitForExit(10000)) { $application.Kill(); throw 'MSI-installed application did not close normally.' }
}
finally
{
if ($null -ne $application -and -not $application.HasExited) { $application.Kill() }
$uninstall = Start-Process msiexec.exe -ArgumentList @('/x', $product, '/qn', '/l*v', ('"' + (Join-Path $env:MSI_ROOT 'uninstall.log') + '"')) -WindowStyle Hidden -PassThru
if (-not $uninstall.WaitForExit(180000)) { $uninstall.Kill(); throw 'MSI uninstall timed out.' }
if ($uninstall.ExitCode -notin 0, 1605) { throw "MSI uninstall failed: $($uninstall.ExitCode)." }
}
if (Test-Path -LiteralPath (Join-Path $target "current/$env:K22_MAIN_EXE")) { throw 'MSI uninstall left the application installed.' }
Write-Output "MSI $product installed, opened its window, closed and uninstalled successfully."
- name: Remove temporary MSI extraction
if: always() && inputs.msi
shell: pwsh
env:
MSI_ROOT: ${{ steps.msi.outputs.root }}
run: |
if ([string]::IsNullOrWhiteSpace($env:MSI_ROOT)) { exit 0 }
$root = [IO.Path]::GetFullPath($env:MSI_ROOT)
$temporaryRoot = [IO.Path]::TrimEndingDirectorySeparator([IO.Path]::GetFullPath($env:RUNNER_TEMP))
if ([IO.Path]::GetDirectoryName($root) -ne $temporaryRoot -or [IO.Path]::GetFileName($root) -notmatch '^k22-msi-[0-9a-f]{32}$') { throw 'Unexpected MSI scratch directory.' }
if (Test-Path -LiteralPath $root) { Remove-Item -LiteralPath $root -Recurse -Force }
- name: Zip CLI
if: inputs.cli-project != ''
shell: pwsh
@@ -617,6 +756,13 @@ jobs:
subject-path: release/*-cli-*.zip
sbom-path: sbom/${{ inputs.app-id }}-cli-${{ needs.build.outputs.version }}.cdx.json
- name: Attest MSI SBOM
if: inputs.msi
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
with:
subject-path: release/*.msi
sbom-path: sbom/${{ inputs.app-id }}-${{ needs.build.outputs.version }}.cdx.json
- name: Upload SBOMs
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
+1 -1
View File
@@ -1 +1 @@
{".":"1.1.0"}
{".":"1.2.0"}
+34
View File
@@ -3,6 +3,40 @@
<!-- Generated by Release Please and GitHub; permit native spacing, links and repeated categories. -->
<!-- markdownlint-disable MD012 MD022 MD024 MD032 MD034 -->
## 1.2.0 (2026-10-13)
<!-- Release notes generated using configuration in .github/release.yml at main -->
## What's Changed
### Changes
* feat(releases): publish signed per-user MSI installers by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/47
**Full Changelog**: https://github.com/KofTwentyTwo/standards/compare/v1.1.2...v1.2.0
## 1.1.2 (2026-10-13)
<!-- Release notes generated using configuration in .github/release.yml at main -->
## What's Changed
### Changes
* fix(releases): scope bootstrap parameters under Actions pwsh by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/45
**Full Changelog**: https://github.com/KofTwentyTwo/standards/compare/v1.1.1...v1.1.2
## 1.1.1 (2026-10-12)
<!-- Release notes generated using configuration in .github/release.yml at main -->
## What's Changed
### Changes
* docs(inventory): register MarkdownViewr and the Windows starter by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/36
* fix(releases): bootstrap verified Artifact Signing dependency by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/44
**Full Changelog**: https://github.com/KofTwentyTwo/standards/compare/v1.1.0...v1.1.1
## 1.1.0 (2026-10-12)
<!-- Release notes generated using configuration in .github/release.yml at main -->
+2
View File
@@ -24,6 +24,8 @@ conformance version in its README once it passes the conformance checker.
| --- | --- | --- | --- |
| [standards](https://github.com/KofTwentyTwo/standards) | These policies, standards, configs, and reusable workflows | Product | Not yet: rulesets and CI being added |
| [AppKit](https://github.com/KofTwentyTwo/AppKit) | Shared foundation packages for Windows desktop apps | Product | Adoption in progress |
| [MarkdownViewr.Windows](https://github.com/KofTwentyTwo/MarkdownViewr.Windows) | Windows Markdown reader based on Darin Kelkhoff's macOS markdownViewr and AppKit | Product | Adoption in progress: release acceptance, signing and maintainer review pending |
| [windows-app-template](https://github.com/KofTwentyTwo/windows-app-template) | Reusable AppKit Windows app starter with optional CLI and stable/dev release workflows | Product | Adoption in progress: shared defaults and release controls in review |
| [gclo](https://github.com/KofTwentyTwo/gclo) | Clone and update every repository of a GitHub organization (Windows app and CLI) | Product | Adoption in progress: standards gates, governance files, and the Kingsrook layout are in review (gclo #62); settings and exceptions pending |
### Awaiting classification
+71
View File
@@ -14,6 +14,14 @@ BeforeAll `
if($start -lt 0 -or $end -ge $lines.Count) { throw 'Cannot locate the actual Velopack signing wrapper.' }
$script:Wrapper = Join-Path $TestDrive 'sign-one.ps1'
[IO.File]::WriteAllLines($script:Wrapper, @($lines[($start + 1)..($end - 1)] | ForEach-Object { $_.Substring(10) }))
$dependencyStart = [Array]::IndexOf($lines, ' - name: Prepare pinned Artifact Signing CLI dependency')
if($dependencyStart -lt 0) { throw 'Cannot locate the actual signing dependency bootstrap.' }
$dependencyStart += 4
$dependencyEnd = $dependencyStart
while($dependencyEnd -lt $lines.Count -and $lines[$dependencyEnd].StartsWith(' ')) { $dependencyEnd++ }
$script:Bootstrap = Join-Path $TestDrive 'sign-dependency.ps1'
# GitHub's default pwsh shell prepends this statement to every inline run body.
[IO.File]::WriteAllLines($script:Bootstrap, @('$ErrorActionPreference = "stop"') + @($lines[$dependencyStart..($dependencyEnd - 1)] | ForEach-Object { $_.Substring(10) }))
$script:OldTool = $env:K22_VPK_SIGN_TOOL
$env:K22_VPK_SIGN_TOOL = 'Invoke-WrapperTool'
@@ -31,6 +39,69 @@ BeforeAll `
}
}
Describe 'The actual Artifact Signing dependency bootstrap' `
{
BeforeEach `
{
$script:InstallRoot = Join-Path $TestDrive ([guid]::NewGuid().ToString('N'))
$script:PackageDirectory = Join-Path $script:InstallRoot 'downloads'
[void][IO.Directory]::CreateDirectory($script:PackageDirectory)
$script:Destination = Join-Path $script:InstallRoot 'ArtifactSigning/sign/sign.0.9.1-beta.26227.3'
Mock Invoke-WebRequest `
{
$fixture = Join-Path (Split-Path -Parent $OutFile) 'fixture'
[void][IO.Directory]::CreateDirectory((Join-Path $fixture 'tools/net8.0/any'))
[IO.File]::WriteAllText((Join-Path $fixture 'tools/net8.0/any/sign.dll'), 'fixture')
[IO.Compression.ZipFile]::CreateFromDirectory($fixture, $OutFile)
}
Mock Get-FileHash { [pscustomobject]@{ Hash = '34FD0D4AEABDBC363A48883881865B4DD65E6C11BA916028082FA23F3E1B1BA1' } }
Mock dotnet { $global:LASTEXITCODE = 0 }
}
It 'stages the verified tool where the official action finds it' `
{
& $script:Bootstrap -InstallRoot $script:InstallRoot -PackageDirectory $script:PackageDirectory
Get-Content -LiteralPath (Join-Path $script:Destination 'tools/net8.0/any/sign.dll') | Should -Be 'fixture'
Should -Invoke dotnet -Exactly 1
}
It 'rejects a changed download before verification or extraction' `
{
Mock Get-FileHash { [pscustomobject]@{ Hash = 'bad' } }
{ & $script:Bootstrap -InstallRoot $script:InstallRoot -PackageDirectory $script:PackageDirectory } | Should -Throw '*checksum mismatch*'
Test-Path -LiteralPath $script:Destination | Should -BeFalse
Should -Invoke dotnet -Exactly 0
}
It 'rejects an invalid NuGet signature before extraction' `
{
Mock dotnet { $global:LASTEXITCODE = 1 }
{ & $script:Bootstrap -InstallRoot $script:InstallRoot -PackageDirectory $script:PackageDirectory } | Should -Throw '*signature verification failed*'
Test-Path -LiteralPath $script:Destination | Should -BeFalse
}
It 'preserves a preexisting dependency directory' `
{
[void][IO.Directory]::CreateDirectory($script:Destination)
$marker = Join-Path $script:Destination 'marker.txt'
[IO.File]::WriteAllText($marker, 'preserve')
{ & $script:Bootstrap -InstallRoot $script:InstallRoot -PackageDirectory $script:PackageDirectory } | Should -Throw '*already exists*'
Get-Content -LiteralPath $marker | Should -Be 'preserve'
Should -Invoke Invoke-WebRequest -Exactly 0
}
It 'rejects a package without the expected executable' `
{
Mock Invoke-WebRequest `
{
$emptyFixture = Join-Path (Split-Path -Parent $OutFile) 'empty'
[void][IO.Directory]::CreateDirectory($emptyFixture)
[IO.Compression.ZipFile]::CreateFromDirectory($emptyFixture, $OutFile)
}
{ & $script:Bootstrap -InstallRoot $script:InstallRoot -PackageDirectory $script:PackageDirectory } | Should -Throw '*missing its executable*'
}
}
AfterAll `
{
$env:K22_VPK_SIGN_TOOL = $script:OldTool
+1 -1
View File
@@ -1 +1 @@
1.1.0
1.2.0