mirror of
https://github.com/ScoopInstaller/Scoop.git
synced 2025-11-27 03:43:23 +00:00
Compare commits
29 Commits
develop
...
feature/in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
362e06c79c | ||
|
|
d9c8f0b3e7 | ||
|
|
5d46f67e34 | ||
|
|
1fae80b2c2 | ||
|
|
cf345e272e | ||
|
|
99fb8383b7 | ||
|
|
0a52f0d0de | ||
|
|
becd429581 | ||
|
|
d3f681ce03 | ||
|
|
9f62e55862 | ||
|
|
887f79d2d7 | ||
|
|
5bae77eebb | ||
|
|
210b5e45a0 | ||
|
|
3aa64baeb1 | ||
|
|
0470ba2d04 | ||
|
|
398aaa2330 | ||
|
|
4585f50a54 | ||
|
|
9a6cdc7328 | ||
|
|
039d2d77c2 | ||
|
|
ec44c6c7b2 | ||
|
|
c264afc6bd | ||
|
|
4061bff270 | ||
|
|
3e3ce096b8 | ||
|
|
83696854a8 | ||
|
|
82a35323d3 | ||
|
|
dfadfcc1dc | ||
|
|
0ade4a5344 | ||
|
|
ce95819fa5 | ||
|
|
c7795b44af |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -2,6 +2,7 @@
|
||||
|
||||
### Features
|
||||
|
||||
- **install:** Add support for historical manifests by git history ([#6370](https://github.com/ScoopInstaller/Scoop/issues/6370))
|
||||
- **install:** Add output for the setting and removal of environment variables ([#6460](https://github.com/ScoopInstaller/Scoop/issues/6460))
|
||||
- **scoop-uninstall:** Allow access to `$bucket` in uninstall scripts ([#6380](https://github.com/ScoopInstaller/Scoop/issues/6380))
|
||||
- **install:** Add separator at the end of notes, highlight suggestions ([#6418](https://github.com/ScoopInstaller/Scoop/issues/6418))
|
||||
@@ -18,24 +19,15 @@
|
||||
- **autoupdate:** Use origin URL to handle URLs with fragment in GitHub mode ([#6455](https://github.com/ScoopInstaller/Scoop/issues/6455))
|
||||
- **buckets|scoop-info:** Switch git log date format to ISO 8601 to avoid locale issues ([#6446](https://github.com/ScoopInstaller/Scoop/issues/6446))
|
||||
- **scoop-version:** Fix logic error caused by missing brackets ([#6463](https://github.com/ScoopInstaller/Scoop/issues/6463))
|
||||
- **getopt:** Teach getopt to respect the `--%` token ([#6477](https://github.com/ScoopInstaller/Scoop/issues/6477))
|
||||
- **core|manifest:** Avoid error messages when searching non-existent 'deprecated' directory ([#6471](https://github.com/ScoopInstaller/Scoop/issues/6471))
|
||||
- **path:** Trim ending slash when initializing paths ([#6501](https://github.com/ScoopInstaller/Scoop/issues/6501))
|
||||
- **checkver:** Allow script to run when URL fetch fails but script exists ([#6490](https://github.com/ScoopInstaller/Scoop/issues/6490), [#6556](https://github.com/ScoopInstaller/Scoop/issues/6556))
|
||||
- **install:** Don't add to `$Error` when checking for service `cexecsvc` ([#6520](https://github.com/ScoopInstaller/Scoop/issues/6520))
|
||||
- **checkver:** Fix incorrect version returned when script fails without output ([#6547](https://github.com/ScoopInstaller/Scoop/issues/6547))
|
||||
- **uninstall:** Import `url_filename` from `download.ps1` ([#6530](https://github.com/ScoopInstaller/Scoop/issues/6530))
|
||||
- **checkver:** Allow script to run when URL fetch fails but script exists ([#6490](https://github.com/ScoopInstaller/Scoop/issues/6490))
|
||||
- **schema:** Add missing `hash.mode` value `github` ([#6533](https://github.com/ScoopInstaller/Scoop/issues/6533))
|
||||
- **core:** Skip NO_JUNCTION logic when $app is 'scoop' in `currentdir` function ([#6541](https://github.com/ScoopInstaller/Scoop/issues/6541))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
- **output:** Replace raw prints with functions for standardized output ([#6449](https://github.com/ScoopInstaller/Scoop/issues/6449))
|
||||
- **output:** Combine the separated outputs into a single output ([#6545](https://github.com/ScoopInstaller/Scoop/issues/6545))
|
||||
|
||||
### Builds
|
||||
|
||||
- **supporting:** Update System.Data.SQLite to 2.0.2 ([#6555](https://github.com/ScoopInstaller/Scoop/issues/6555), [#6560](https://github.com/ScoopInstaller/Scoop/issues/6560))
|
||||
|
||||
## [v0.5.3](https://github.com/ScoopInstaller/Scoop/compare/v0.5.2...v0.5.3) - 2025-08-11
|
||||
|
||||
|
||||
@@ -274,8 +274,6 @@ while ($in_progress -gt 0) {
|
||||
$expected_ver = $json.version
|
||||
$ver = $Version
|
||||
|
||||
$matchesHashtable = @{}
|
||||
|
||||
if (!$ver) {
|
||||
if (!$regexp -and $replace) {
|
||||
next "'replace' requires 're' or 'regex'"
|
||||
@@ -292,9 +290,6 @@ while ($in_progress -gt 0) {
|
||||
}
|
||||
}
|
||||
|
||||
$page = $null
|
||||
$source = $url
|
||||
|
||||
if ($url -and !$err) {
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$ms.Write($result, 0, $result.Length)
|
||||
@@ -304,17 +299,12 @@ while ($in_progress -gt 0) {
|
||||
}
|
||||
$page = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
|
||||
}
|
||||
|
||||
$source = $url
|
||||
if ($script) {
|
||||
$page = Invoke-Command ([scriptblock]::Create($script -join "`r`n"))
|
||||
$source = 'the output of script'
|
||||
}
|
||||
|
||||
if ($null -eq $page) {
|
||||
next "couldn't retrieve content from $source"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($jsonpath) {
|
||||
# Return only a single value if regex is absent
|
||||
$noregex = [String]::IsNullOrEmpty($regexp)
|
||||
@@ -373,6 +363,7 @@ while ($in_progress -gt 0) {
|
||||
}
|
||||
|
||||
if ($match -and $match.Success) {
|
||||
$matchesHashtable = @{}
|
||||
$re.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) }
|
||||
$ver = $matchesHashtable['1']
|
||||
if ($replace) {
|
||||
|
||||
@@ -160,7 +160,7 @@ function add_bucket($name, $repo) {
|
||||
Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
return 1
|
||||
}
|
||||
Write-Host 'OK.'
|
||||
Write-Host 'OK'
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
info 'Updating cache...'
|
||||
Set-ScoopDB -Path (Get-ChildItem (Find-BucketDirectory $name) -Filter '*.json' -Recurse).FullName
|
||||
|
||||
11
lib/core.ps1
11
lib/core.ps1
@@ -206,6 +206,9 @@ function Complete-ConfigChange {
|
||||
}
|
||||
|
||||
if ($Name -eq 'use_sqlite_cache' -and $Value -eq $true) {
|
||||
if ((Get-DefaultArchitecture) -eq 'arm64') {
|
||||
abort 'SQLite cache is not supported on ARM64 platform.'
|
||||
}
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
info 'Initializing SQLite cache in progress... This may take a while, please wait.'
|
||||
@@ -747,7 +750,7 @@ function Invoke-ExternalCommand {
|
||||
[void]$Process.Start()
|
||||
} catch {
|
||||
if ($Activity) {
|
||||
Write-Host "Error." -ForegroundColor DarkRed
|
||||
Write-Host "error." -ForegroundColor DarkRed
|
||||
}
|
||||
error $_.Exception.Message
|
||||
return $false
|
||||
@@ -766,20 +769,20 @@ function Invoke-ExternalCommand {
|
||||
if ($Process.ExitCode -ne 0) {
|
||||
if ($ContinueExitCodes -and ($ContinueExitCodes.ContainsKey($Process.ExitCode))) {
|
||||
if ($Activity) {
|
||||
Write-Host "Done." -ForegroundColor DarkYellow
|
||||
Write-Host "done." -ForegroundColor DarkYellow
|
||||
}
|
||||
warn $ContinueExitCodes[$Process.ExitCode]
|
||||
return $true
|
||||
} else {
|
||||
if ($Activity) {
|
||||
Write-Host "Error." -ForegroundColor DarkRed
|
||||
Write-Host "error." -ForegroundColor DarkRed
|
||||
}
|
||||
error "Exit code was $($Process.ExitCode)!"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
if ($Activity) {
|
||||
Write-Host "Done." -ForegroundColor Green
|
||||
Write-Host "done." -ForegroundColor Green
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
.SYNOPSIS
|
||||
Get SQLite .NET driver
|
||||
.DESCRIPTION
|
||||
Download and extract the SQLite .NET driver and SQLite precompiled binaries.
|
||||
The SQLite version is automatically detected from the download page.
|
||||
Download and extract the SQLite .NET driver from NuGet.
|
||||
.PARAMETER Version
|
||||
System.String
|
||||
The version of the System.Data.SQLite NuGet package to download. (require version 2.0.0 or higher)
|
||||
The version of the SQLite .NET driver to download.
|
||||
.INPUTS
|
||||
None
|
||||
.OUTPUTS
|
||||
@@ -17,44 +16,21 @@
|
||||
#>
|
||||
function Get-SQLite {
|
||||
param (
|
||||
[string]$Version = '2.0.2'
|
||||
[string]$Version = '1.0.118'
|
||||
)
|
||||
# Install SQLite
|
||||
try {
|
||||
$sqliteNetPath = "$env:TEMP\sqlite.net.zip"
|
||||
$sqliteDllPath = "$env:TEMP\sqlite.dll.zip"
|
||||
Write-Host "Downloading SQLite $Version..." -ForegroundColor DarkYellow
|
||||
$sqlitePkgPath = "$env:TEMP\sqlite.zip"
|
||||
$sqliteTempPath = "$env:TEMP\sqlite"
|
||||
$sqlitePath = "$PSScriptRoot\..\supporting\sqlite"
|
||||
|
||||
$arch = switch (Get-DefaultArchitecture) {
|
||||
'32bit' { 'x86' }
|
||||
'64bit' { 'x64' }
|
||||
'arm64' { 'arm64' }
|
||||
default { Write-Warning "Unknown architecture, using x64 as fallback"; 'x64' }
|
||||
}
|
||||
|
||||
Write-Host "Downloading System.Data.SQLite $Version..." -ForegroundColor DarkYellow
|
||||
Invoke-WebRequest -Uri "https://globalcdn.nuget.org/packages/system.data.sqlite.$Version.nupkg" -OutFile $sqliteNetPath
|
||||
|
||||
$downloadPage = Invoke-WebRequest -Uri 'https://sqlite.org/download.html' -UseBasicParsing
|
||||
if ($downloadPage.Content -match '(?s)<!-- Download product data.*?(PRODUCT,.+?)-->') {
|
||||
$productData = $Matches[1] | ConvertFrom-Csv
|
||||
} else {
|
||||
throw "Failed to parse SQLite download page product data"
|
||||
}
|
||||
$matchRow = $productData | Where-Object { $_.'RELATIVE-URL' -match "sqlite-dll-win-$arch-" }
|
||||
if (-not $matchRow) {
|
||||
throw "SQLite DLL for architecture $arch not found"
|
||||
}
|
||||
Write-Host "Downloading SQLite DLL $($matchRow.VERSION)..." -ForegroundColor DarkYellow
|
||||
Invoke-WebRequest -Uri "https://sqlite.org/$($matchRow.'RELATIVE-URL')" -OutFile $sqliteDllPath
|
||||
|
||||
Write-Host "Extracting libraries... " -ForegroundColor DarkYellow -NoNewline
|
||||
$sqliteNetPath, $sqliteDllPath | Expand-Archive -DestinationPath $sqliteTempPath -Force
|
||||
$null = New-Item -Path "$sqlitePath\$arch" -ItemType Directory -Force
|
||||
Move-Item -Path "$sqliteTempPath\lib\netstandard2.0\System.Data.SQLite.dll" -Destination $sqlitePath -Force
|
||||
Move-Item -Path "$sqliteTempPath\sqlite3.dll" -Destination "$sqlitePath\$arch\e_sqlite3.dll" -Force
|
||||
Remove-Item -Path $sqliteNetPath, $sqliteDllPath, $sqliteTempPath -Recurse -Force
|
||||
Write-Host 'Done.' -ForegroundColor DarkYellow
|
||||
Invoke-WebRequest -Uri "https://api.nuget.org/v3-flatcontainer/stub.system.data.sqlite.core.netframework/$version/stub.system.data.sqlite.core.netframework.$version.nupkg" -OutFile $sqlitePkgPath
|
||||
Write-Host "Extracting SQLite $Version..." -ForegroundColor DarkYellow -NoNewline
|
||||
Expand-Archive -Path $sqlitePkgPath -DestinationPath $sqliteTempPath -Force
|
||||
New-Item -Path $sqlitePath -ItemType Directory -Force | Out-Null
|
||||
Move-Item -Path "$sqliteTempPath\build\net451\*", "$sqliteTempPath\lib\net451\System.Data.SQLite.dll" -Destination $sqlitePath -Force
|
||||
Remove-Item -Path $sqlitePkgPath, $sqliteTempPath -Recurse -Force
|
||||
Write-Host ' Done' -ForegroundColor DarkYellow
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
@@ -243,9 +219,9 @@ function Set-ScoopDB {
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Find Scoop database item(s).
|
||||
Select Scoop database item(s).
|
||||
.DESCRIPTION
|
||||
Find item(s) from the Scoop SQLite database.
|
||||
Select item(s) from the Scoop SQLite database.
|
||||
The pattern is matched against the name, binaries, and shortcuts columns for apps.
|
||||
.PARAMETER Pattern
|
||||
System.String
|
||||
@@ -257,9 +233,9 @@ function Set-ScoopDB {
|
||||
System.String
|
||||
.OUTPUTS
|
||||
System.Data.DataTable
|
||||
The found database item(s).
|
||||
The selected database item(s).
|
||||
#>
|
||||
function Find-ScoopDBItem {
|
||||
function Select-ScoopDBItem {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
|
||||
@@ -288,10 +264,11 @@ function Find-ScoopDBItem {
|
||||
[void]$dbAdapter.Fill($result)
|
||||
}
|
||||
end {
|
||||
$dbCommand.Dispose()
|
||||
$resultCopy = $result.Copy()
|
||||
$dbAdapter.Dispose()
|
||||
$db.Dispose()
|
||||
return $result
|
||||
# Use Write-Output to ensure PowerShell properly returns the DataTable
|
||||
Write-Output $resultCopy -NoEnumerate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,13 +295,13 @@ function Find-ScoopDBItem {
|
||||
function Get-ScoopDBItem {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
|
||||
[Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
||||
[string]
|
||||
$Name,
|
||||
[Parameter(Mandatory, Position = 1)]
|
||||
[Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
|
||||
[string]
|
||||
$Bucket,
|
||||
[Parameter(Position = 2)]
|
||||
[Parameter(Position = 2, ValueFromPipelineByPropertyName)]
|
||||
[string]
|
||||
$Version
|
||||
)
|
||||
@@ -332,31 +309,46 @@ function Get-ScoopDBItem {
|
||||
begin {
|
||||
$db = Open-ScoopDB
|
||||
$dbAdapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter
|
||||
$dbCommand = $db.CreateCommand()
|
||||
$dbCommand.CommandType = [System.Data.CommandType]::Text
|
||||
$dbAdapter.SelectCommand = $dbCommand
|
||||
}
|
||||
|
||||
process {
|
||||
$result = New-Object System.Data.DataTable
|
||||
|
||||
# Build query based on whether version is specified
|
||||
$dbQuery = 'SELECT * FROM app WHERE name = @Name AND bucket = @Bucket'
|
||||
if ($Version) {
|
||||
$dbQuery += ' AND version = @Version'
|
||||
} else {
|
||||
$dbQuery += ' ORDER BY version DESC LIMIT 1'
|
||||
}
|
||||
$dbCommand = $db.CreateCommand()
|
||||
|
||||
$dbCommand.CommandText = $dbQuery
|
||||
$dbCommand.CommandType = [System.Data.CommandType]::Text
|
||||
$dbAdapter.SelectCommand = $dbCommand
|
||||
}
|
||||
process {
|
||||
$dbCommand.Parameters.Clear()
|
||||
|
||||
# Add parameters for this specific query
|
||||
$dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null
|
||||
$dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null
|
||||
if ($Version) {
|
||||
$dbCommand.Parameters.AddWithValue('@Version', $Version) | Out-Null
|
||||
}
|
||||
|
||||
[void]$dbAdapter.Fill($result)
|
||||
|
||||
# Create a copy of the DataTable to avoid disposal issues
|
||||
$resultCopy = $result.Copy()
|
||||
|
||||
# Use Write-Output to ensure PowerShell properly returns the DataTable
|
||||
Write-Output $resultCopy -NoEnumerate
|
||||
}
|
||||
|
||||
end {
|
||||
$dbCommand.Dispose()
|
||||
# Clean up all resources
|
||||
$dbAdapter.Dispose()
|
||||
$dbCommand.Dispose()
|
||||
$db.Dispose()
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,11 @@ function Invoke-Extraction {
|
||||
DestinationPath = Join-Path $Path $extractTo[$extracted]
|
||||
ExtractDir = $extractDir[$extracted]
|
||||
}
|
||||
Write-Host "Extracting $([char]0x1b)[36m$(url_remote_filename $uri[$i])$([char]0x1b)[0m... " -NoNewline
|
||||
Write-Host 'Extracting ' -NoNewline
|
||||
Write-Host $(url_remote_filename $uri[$i]) -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' ... ' -NoNewline
|
||||
& $extractFn @fnArgs -Removal
|
||||
Write-Host 'Done.' -ForegroundColor Green
|
||||
Write-Host 'done.' -ForegroundColor Green
|
||||
$extracted++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +391,9 @@ function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $
|
||||
}
|
||||
|
||||
if ((Test-Path $data.$url.source) -and -not((Test-Path "$($data.$url.source).aria2") -or (Test-Path $urlstxt)) -and $use_cache) {
|
||||
Write-Host "Loading $([char]0x1b)[36m$(url_remote_filename $url)$([char]0x1b)[0m from cache."
|
||||
Write-Host 'Loading ' -NoNewline
|
||||
Write-Host $(url_remote_filename $url) -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' from cache.'
|
||||
} else {
|
||||
$download_finished = $false
|
||||
# create aria2 input file content
|
||||
@@ -423,7 +425,7 @@ function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $
|
||||
$aria2 = "& '$(Get-HelperPath -Helper Aria2)' $($options -join ' ')"
|
||||
|
||||
# handle aria2 console output
|
||||
Write-Host 'Starting download with aria2...'
|
||||
Write-Host 'Starting download with aria2 ...'
|
||||
|
||||
# Set console output encoding to UTF8 for non-ASCII characters printing
|
||||
$oriConsoleEncoding = [Console]::OutputEncoding
|
||||
@@ -464,7 +466,7 @@ function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $
|
||||
warn $urlstxt_content
|
||||
warn $aria2
|
||||
|
||||
Write-Host 'Fallback to default downloader...'
|
||||
Write-Host 'Fallback to default downloader ...'
|
||||
|
||||
try {
|
||||
foreach ($url in $urls) {
|
||||
@@ -729,7 +731,9 @@ function check_hash($file, $hash, $app_name) {
|
||||
return $true, $null
|
||||
}
|
||||
|
||||
Write-Host "Checking hash of $([char]0x1b)[36m$(url_remote_filename $url)$([char]0x1b)[0m... " -NoNewline
|
||||
Write-Host 'Checking hash of ' -NoNewline
|
||||
Write-Host $(url_remote_filename $url) -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' ... ' -NoNewline
|
||||
$algorithm, $expected = get_hash $hash
|
||||
if ($null -eq $algorithm) {
|
||||
return $false, "Hash type '$algorithm' isn't supported."
|
||||
@@ -751,7 +755,7 @@ function check_hash($file, $hash, $app_name) {
|
||||
}
|
||||
return $false, $msg
|
||||
}
|
||||
Write-Host 'OK.' -f Green
|
||||
Write-Host 'ok.' -f Green
|
||||
return $true, $null
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
# a parameter should end with '='
|
||||
# returns @(opts hash, remaining_args array, error string)
|
||||
# NOTES:
|
||||
# The first "--" or "--%" in $argv, if any, will terminate all options; any
|
||||
# following arguments are treated as non-option arguments, even if
|
||||
# they begin with a hyphen. The terminator token itself ("--" or "--%")
|
||||
# will not be included. (POSIX-compatible)
|
||||
# The first "--" in $argv, if any, will terminate all options; any
|
||||
# following arguments are treated as non-option arguments, even if
|
||||
# they begin with a hyphen. The "--" itself will not be included in
|
||||
# the returned $opts. (POSIX-compatible)
|
||||
function getopt([String[]]$argv, [String]$shortopts, [String[]]$longopts) {
|
||||
$opts = @{}; $rem = @()
|
||||
|
||||
@@ -32,7 +32,7 @@ function getopt([String[]]$argv, [String]$shortopts, [String[]]$longopts) {
|
||||
if ($arg -is [Int]) { $rem += $arg; continue }
|
||||
if ($arg -is [Decimal]) { $rem += $arg; continue }
|
||||
|
||||
if ($arg -eq '--' -or $arg -eq '--%') {
|
||||
if ($arg -eq '--') {
|
||||
if ($i -lt $argv.Length - 1) {
|
||||
$rem += $argv[($i + 1)..($argv.Length - 1)]
|
||||
}
|
||||
|
||||
@@ -165,9 +165,9 @@ function Invoke-HookScript {
|
||||
$script = $script.script
|
||||
}
|
||||
if ($script) {
|
||||
Write-Host "Running $HookType script... " -NoNewline
|
||||
Write-Host "Running $HookType script..." -NoNewline
|
||||
Invoke-Command ([scriptblock]::Create($script -join "`r`n"))
|
||||
Write-Host 'Done.' -ForegroundColor Green
|
||||
Write-Host 'done.' -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ function test_running_process($app, $global) {
|
||||
# Required to handle docker/for-win#12240
|
||||
function New-DirectoryJunction($source, $target) {
|
||||
# test if this script is being executed inside a docker container
|
||||
if (Get-Service -Name cexecsvc -ErrorAction Ignore) {
|
||||
if (Get-Service -Name cexecsvc -ErrorAction SilentlyContinue) {
|
||||
cmd.exe /d /c "mklink /j `"$source`" `"$target`""
|
||||
} else {
|
||||
New-Item -Path $source -ItemType Junction -Value $target
|
||||
|
||||
213
lib/manifest.ps1
213
lib/manifest.ps1
@@ -18,8 +18,8 @@ function url_manifest($url) {
|
||||
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
||||
$data = $wc.DownloadData($url)
|
||||
$str = (Get-Encoding($wc)).GetString($data)
|
||||
} catch [System.Management.Automation.MethodInvocationException] {
|
||||
error $_.Exception.InnerException.Message
|
||||
} catch [system.management.automation.methodinvocationexception] {
|
||||
warn "error: $($_.exception.innerexception.message)"
|
||||
} catch {
|
||||
throw
|
||||
}
|
||||
@@ -172,38 +172,200 @@ function Get-SupportedArchitecture($manifest, $architecture) {
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RelativePathCompat($from, $to) {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Cross-platform compatible relative path function
|
||||
.DESCRIPTION
|
||||
Falls back to custom implementation for Windows PowerShell compatibility
|
||||
#>
|
||||
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||||
# PowerShell Core/7+ - use built-in method
|
||||
try {
|
||||
return [System.IO.Path]::GetRelativePath($from, $to)
|
||||
} catch {
|
||||
# Fallback if method fails
|
||||
}
|
||||
}
|
||||
|
||||
# Windows PowerShell compatible implementation
|
||||
$fromUri = New-Object System.Uri($from.TrimEnd('\') + '\')
|
||||
$toUri = New-Object System.Uri($to)
|
||||
|
||||
if ($fromUri.Scheme -ne $toUri.Scheme) {
|
||||
return $to # Cannot make relative path between different schemes
|
||||
}
|
||||
|
||||
$relativeUri = $fromUri.MakeRelativeUri($toUri)
|
||||
$relativePath = [System.Uri]::UnescapeDataString($relativeUri.ToString())
|
||||
|
||||
return $relativePath -replace '/', '\'
|
||||
}
|
||||
|
||||
function Find-HistoricalManifestInCache($app, $bucket, $requestedVersion) {
|
||||
if (!(get_config USE_SQLITE_CACHE)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Return null if bucket is null or empty
|
||||
if (!$bucket) {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Import database functions if not already loaded
|
||||
if (!(Get-Command 'Get-ScoopDBItem' -ErrorAction SilentlyContinue)) {
|
||||
. "$PSScriptRoot\database.ps1"
|
||||
}
|
||||
|
||||
$dbResult = Get-ScoopDBItem -Name $app -Bucket $bucket -Version $requestedVersion
|
||||
|
||||
# Strictly follow DB contract: must be DataTable with at least one row
|
||||
if (-not ($dbResult -is [System.Data.DataTable])) { return $null }
|
||||
if ($dbResult.Rows.Count -eq 0) { return $null }
|
||||
|
||||
$manifestText = $dbResult.Rows[0]['manifest']
|
||||
if ([string]::IsNullOrWhiteSpace($manifestText)) { return $null }
|
||||
|
||||
$manifestObj = $null
|
||||
try { $manifestObj = $manifestText | ConvertFrom-Json -ErrorAction Stop } catch {}
|
||||
$manifestVersion = if ($manifestObj -and $manifestObj.version) { $manifestObj.version } else { $requestedVersion }
|
||||
|
||||
return @{ ManifestText = $manifestText; version = $manifestVersion; source = "sqlite_exact_match" }
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-HistoricalManifestInGit($app, $bucket, $requestedVersion) {
|
||||
# Only proceed if git history is enabled
|
||||
if (!(get_config USE_GIT_HISTORY $true)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if (!$bucket) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$bucketDir = Find-BucketDirectory $bucket -Root
|
||||
if (!(Test-Path "$bucketDir\.git")) {
|
||||
warn "Bucket '$bucket' is not a git repository. Cannot search historical versions."
|
||||
return $null
|
||||
}
|
||||
|
||||
$manifestPath = "$app.json"
|
||||
$innerBucketDir = Find-BucketDirectory $bucket # Non-root path
|
||||
|
||||
if (-not (Test-Path $innerBucketDir -PathType Container)) {
|
||||
warn "Could not find inner bucket directory for '$bucket' at '$innerBucketDir'."
|
||||
return $null
|
||||
}
|
||||
$relativeManifestPath = Get-RelativePathCompat $bucketDir (Join-Path $innerBucketDir $manifestPath)
|
||||
$relativeManifestPath = $relativeManifestPath -replace '\\', '/'
|
||||
|
||||
try {
|
||||
# Prefer precise regex match on version line, fallback to -S literal
|
||||
$pattern = '"version"\s*:\s*"' + [regex]::Escape($requestedVersion) + '"'
|
||||
$commits = @()
|
||||
$outG = Invoke-Git -Path $bucketDir -ArgumentList @('log','--follow','-n','1','--format=%H','-G',$pattern,'--',$relativeManifestPath)
|
||||
if ($outG) { $commits = @($outG | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
||||
|
||||
if ($commits.Count -eq 0) {
|
||||
$searchLiteral = '"version": "' + $requestedVersion + '"'
|
||||
$outS = Invoke-Git -Path $bucketDir -ArgumentList @('log','--follow','-n','1','--format=%H','-S',$searchLiteral,'--',$relativeManifestPath)
|
||||
if ($outS) { $commits = @($outS | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
||||
}
|
||||
|
||||
if ($commits.Count -eq 0) { return $null }
|
||||
|
||||
$h = $commits[0]
|
||||
|
||||
# First try parent snapshot (latest state before change), then the change itself
|
||||
foreach ($spec in @("$h^","$h")) {
|
||||
$content = Invoke-Git -Path $bucketDir -ArgumentList @('show', "$spec`:$relativeManifestPath")
|
||||
if (-not $content -or ($LASTEXITCODE -ne 0)) { continue }
|
||||
if ($content -is [Array]) { $content = $content -join "`n" }
|
||||
try {
|
||||
$obj = $content | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch { continue }
|
||||
if ($obj -and $obj.version -eq $requestedVersion) {
|
||||
return @{ ManifestText = $content; version = $requestedVersion; source = "git_manifest:$spec" }
|
||||
}
|
||||
}
|
||||
|
||||
# Fallback: iterate recent commits that touched the version string and validate
|
||||
$outAll = Invoke-Git -Path $bucketDir -ArgumentList @('log','--follow','--format=%H','-G',$pattern,'--',$relativeManifestPath)
|
||||
$allCommits = @()
|
||||
if ($outAll) { $allCommits = @($outAll | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
||||
|
||||
foreach ($c in $allCommits) {
|
||||
$content = Invoke-Git -Path $bucketDir -ArgumentList @('show', "$c`:$relativeManifestPath")
|
||||
if (-not $content -or ($LASTEXITCODE -ne 0)) { continue }
|
||||
if ($content -is [Array]) { $content = $content -join "`n" }
|
||||
try { $obj = $content | ConvertFrom-Json -ErrorAction Stop } catch { continue }
|
||||
if ($obj -and $obj.version -eq $requestedVersion) {
|
||||
return @{ ManifestText = $content; version = $requestedVersion; source = "git_manifest:$c" }
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
} catch { return $null }
|
||||
}
|
||||
|
||||
function Find-HistoricalManifest($app, $bucket, $version) {
|
||||
# Orchestrates historical manifest lookup using available providers (DB → Git)
|
||||
$result = $null
|
||||
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
$result = Find-HistoricalManifestInCache $app $bucket $version
|
||||
if ($result) {
|
||||
if ($result.ManifestText) {
|
||||
$path = Write-ManifestToUserCache -App $app -ManifestText $result.ManifestText
|
||||
return @{ path = $path; version = $result.version; source = $result.source }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
if (get_config USE_GIT_HISTORY $true) {
|
||||
$result = Find-HistoricalManifestInGit $app $bucket $version
|
||||
if ($result) {
|
||||
if ($result.ManifestText) {
|
||||
$path = Write-ManifestToUserCache -App $app -ManifestText $result.ManifestText
|
||||
return @{ path = $path; version = $result.version; source = $result.source }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
|
||||
function generate_user_manifest($app, $bucket, $version) {
|
||||
# 'autoupdate.ps1' 'buckets.ps1' 'manifest.ps1'
|
||||
$app, $manifest, $bucket, $null = Get-Manifest "$bucket/$app"
|
||||
if ("$($manifest.version)" -eq "$version") {
|
||||
return manifest_path $app $bucket
|
||||
}
|
||||
warn "Given version ($version) does not match manifest ($($manifest.version))"
|
||||
warn "Attempting to generate manifest for '$app' ($version)"
|
||||
|
||||
# Try historical providers via orchestrator
|
||||
$historicalResult = Find-HistoricalManifest $app $bucket $version
|
||||
if ($historicalResult) { return $historicalResult.path }
|
||||
|
||||
# No historical manifest; try autoupdate if available
|
||||
if (!($manifest.autoupdate)) {
|
||||
abort "Could not find manifest for '$app@$version' and no autoupdate is available"
|
||||
}
|
||||
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$manifest_path = "$(usermanifestsdir)\$app.json"
|
||||
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
$cached_manifest = (Get-ScoopDBItem -Name $app -Bucket $bucket -Version $version).manifest
|
||||
if ($cached_manifest) {
|
||||
$cached_manifest | Out-UTF8File $manifest_path
|
||||
return $manifest_path
|
||||
}
|
||||
}
|
||||
|
||||
if (!($manifest.autoupdate)) {
|
||||
abort "'$app' does not have autoupdate capability`r`ncouldn't find manifest for '$app@$version'"
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-AutoUpdate $app $manifest_path $manifest $version $(@{ })
|
||||
return $manifest_path
|
||||
} catch {
|
||||
Write-Host -ForegroundColor DarkRed "Could not install $app@$version"
|
||||
warn "Autoupdate failed for '$app@$version'"
|
||||
abort "Installation of '$app@$version' is not possible"
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function url($manifest, $arch) { arch_specific 'url' $manifest $arch }
|
||||
@@ -212,3 +374,16 @@ function uninstaller($manifest, $arch) { arch_specific 'uninstaller' $manifest $
|
||||
function hash($manifest, $arch) { arch_specific 'hash' $manifest $arch }
|
||||
function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch }
|
||||
function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch }
|
||||
|
||||
# Helper: write manifest text to user manifests cache directory and return path
|
||||
function Write-ManifestToUserCache {
|
||||
param(
|
||||
[Parameter(Mandatory=$true, Position=0)][string]$App,
|
||||
[Parameter(Mandatory=$true, Position=1)][string]$ManifestText
|
||||
)
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$tempManifestPath = "$(usermanifestsdir)\$App.json"
|
||||
$ManifestText | Out-UTF8File -FilePath $tempManifestPath
|
||||
return $tempManifestPath
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
# use_sqlite_cache: $true|$false
|
||||
# Use SQLite database for caching. This is useful for speeding up 'scoop search' and 'scoop shim' commands.
|
||||
#
|
||||
# use_git_history: $true|$false
|
||||
# Enable searching for specific versions in git history when installing apps with version specifiers.
|
||||
# When enabled, Scoop will first search the bucket's git history for the exact version before falling back to autoupdate.
|
||||
# (Default is $true)
|
||||
#
|
||||
# no_junction: $true|$false
|
||||
# The 'current' version alias will not be used. Shims and shortcuts will point to specific version instead.
|
||||
#
|
||||
@@ -169,7 +174,7 @@ if (!$name) {
|
||||
Write-Host "'$name' has been set to '$value'"
|
||||
} else {
|
||||
$value = get_config $name
|
||||
if($null -eq $value) {
|
||||
if ($null -eq $value) {
|
||||
Write-Host "'$name' is not set"
|
||||
} else {
|
||||
if ($value -is [System.DateTime]) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# scoop install git
|
||||
#
|
||||
# To install a different version of the app
|
||||
# (note that this will auto-generate the manifest using current version):
|
||||
# (will search sqlite cache if enabled, then git history (if use_git_history is $true), then auto-generate manifest as fallback):
|
||||
# scoop install gh@2.7.0
|
||||
#
|
||||
# To install an app from a manifest at a URL:
|
||||
|
||||
@@ -140,7 +140,8 @@ function search_remotes($query) {
|
||||
} | Where-Object { $_.results }
|
||||
|
||||
if ($results.count -gt 0) {
|
||||
Write-Host "Results from other known buckets...`n(add them using 'scoop bucket add <bucket name>')"
|
||||
Write-Host "Results from other known buckets...
|
||||
(add them using 'scoop bucket add <bucket name>')"
|
||||
}
|
||||
|
||||
$remote_list = @()
|
||||
@@ -158,7 +159,7 @@ function search_remotes($query) {
|
||||
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
Find-ScoopDBItem $query -From @('name', 'binary', 'shortcut') |
|
||||
Select-ScoopDBItem $query -From @('name', 'binary', 'shortcut') |
|
||||
Select-Object -Property name, version, bucket, binary |
|
||||
ForEach-Object {
|
||||
$list.Add([PSCustomObject]@{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# To add a custom shim, use the 'add' subcommand:
|
||||
#
|
||||
# scoop shim add <shim_name> <command_path> [[--|--%] <args>...]
|
||||
# scoop shim add <shim_name> <command_path> [<args>...]
|
||||
#
|
||||
# To remove shims, use the 'rm' subcommand: (CAUTION: this could remove shims added by an app manifest)
|
||||
#
|
||||
@@ -25,15 +25,11 @@
|
||||
# Options:
|
||||
# -g, --global Manipulate global shim(s)
|
||||
#
|
||||
# HINT: The FIRST terminator token ('--' or PowerShell '--%'), if any, is treated as the option
|
||||
# terminator and will NOT be included; everything after it is passed to the shim.
|
||||
# So if you want to pass arguments like '-g' or '--global' to the shim, put them after a '--' or '--%'.
|
||||
# Examples:
|
||||
# POSIX-style: scoop shim add myapp 'D:\path\myapp.exe' '--' myapp_args --global
|
||||
# PowerShell: scoop shim add myapp D:\path\myapp.exe --% myapp_args --global
|
||||
# Notes:
|
||||
# - In PowerShell, '--' should be quoted to avoid parsing.
|
||||
# - '--%' disables PowerShell parsing of the remainder; pass literals as needed.
|
||||
# HINT: The FIRST double-hyphen '--', if any, will be treated as the POSIX-style command option terminator
|
||||
# and will NOT be included in arguments, so if you want to pass arguments like '-g' or '--global' to
|
||||
# the shim, put them after a '--'. Note that in PowerShell, you must use a QUOTED '--', e.g.,
|
||||
#
|
||||
# scoop shim add myapp 'D:\path\myapp.exe' '--' myapp_args --global
|
||||
|
||||
param($SubCommand)
|
||||
|
||||
@@ -116,10 +112,13 @@ switch ($SubCommand) {
|
||||
}
|
||||
}
|
||||
if ($commandPath -and (Test-Path $commandPath)) {
|
||||
Write-Host "Adding $(if ($global) { 'global' } else { 'local' }) shim $([char]0x1b)[36m$shimName$([char]0x1b)[0m..."
|
||||
Write-Host "Adding $(if ($global) { 'global' } else { 'local' }) shim " -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host '...'
|
||||
shim $commandPath $global $shimName $commandArgs
|
||||
} else {
|
||||
error "Command path does not exist: $([char]0x1b)[36m$($other[1])$([char]0x1b)[31m"
|
||||
Write-Host "ERROR: Command path does not exist: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $($other[1]) -ForegroundColor Cyan
|
||||
exit 3
|
||||
}
|
||||
}
|
||||
@@ -134,7 +133,8 @@ switch ($SubCommand) {
|
||||
}
|
||||
if ($failed) {
|
||||
$failed | ForEach-Object {
|
||||
error "$(if ($global) { 'Global' } else { 'Local' }) shim not found: $([char]0x1b)[36m$_$([char]0x1b)[31m"
|
||||
Write-Host "ERROR: $(if ($global) { 'Global' } else {'Local' }) shim not found: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $_ -ForegroundColor Cyan
|
||||
}
|
||||
exit 3
|
||||
}
|
||||
@@ -147,7 +147,7 @@ switch ($SubCommand) {
|
||||
$pattern = $_
|
||||
[void][Regex]::New($pattern)
|
||||
} catch {
|
||||
error "Invalid pattern: $([char]0x1b)[35m$pattern$([char]0x1b)[31m"
|
||||
error "Invalid pattern: $([char]0x1b)[35m$pattern$([char]0x1b)[0m"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
@@ -171,9 +171,11 @@ switch ($SubCommand) {
|
||||
if ($shimPath) {
|
||||
Get-ShimInfo $shimPath
|
||||
} else {
|
||||
error "$(if ($global) { 'Global' } else { 'Local' }) shim not found: $([char]0x1b)[36m$shimName$([char]0x1b)[31m"
|
||||
Write-Host "ERROR: $(if ($global) { 'Global' } else { 'Local' }) shim not found: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan
|
||||
if (Get-ShimPath $shimName (!$global)) {
|
||||
Write-Host "But a $(if ($global) { 'local' } else { 'global' }) shim exists, run 'scoop shim info $shimName$(if (!$global) { ' --global' })' to show its info."
|
||||
Write-Host "But a $(if ($global) { 'local' } else {'global' }) shim exists, " -NoNewline
|
||||
Write-Host "run 'scoop shim info $shimName$(if (!$global) { ' --global' })' to show its info"
|
||||
exit 2
|
||||
}
|
||||
exit 3
|
||||
@@ -185,7 +187,9 @@ switch ($SubCommand) {
|
||||
if ($shimPath) {
|
||||
$shimInfo = Get-ShimInfo $shimPath
|
||||
if ($null -eq $shimInfo.Alternatives) {
|
||||
error "No alternatives of $([char]0x1b)[36m$shimName$([char]0x1b)[31m found."
|
||||
Write-Host 'ERROR: No alternatives of ' -ForegroundColor Red -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' found.' -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
$shimInfo.Alternatives = $shimInfo.Alternatives.Split(' ')
|
||||
@@ -194,10 +198,18 @@ switch ($SubCommand) {
|
||||
}
|
||||
$selected = $Host.UI.PromptForChoice("Alternatives of '$shimName' command", "Please choose one that provides '$shimName' as default:", $altApps, 0)
|
||||
if ($selected -eq 0) {
|
||||
Write-Host "$([char]0x1b)[36m$shimName$([char]0x1b)[0m is already from $([char]0x1b)[33m$($shimInfo.Source)$([char]0x1b)[0m, nothing changed."
|
||||
Write-Host 'INFO: ' -ForegroundColor Blue -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' is already from ' -NoNewline
|
||||
Write-Host $shimInfo.Source -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host ', nothing changed.'
|
||||
} else {
|
||||
$newApp = $shimInfo.Alternatives[$selected]
|
||||
Write-Host "Use $([char]0x1b)[36m$shimName$([char]0x1b)[0m from $([char]0x1b)[33m$newApp$([char]0x1b)[0m as default... " -NoNewline
|
||||
Write-Host 'Use ' -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' from ' -NoNewline
|
||||
Write-Host $newApp -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host ' as default...' -NoNewline
|
||||
$pathNoExt = strip_ext $shimPath
|
||||
'', '.shim', '.cmd', '.ps1' | ForEach-Object {
|
||||
$oldShimPath = "$pathNoExt$_"
|
||||
@@ -209,12 +221,14 @@ switch ($SubCommand) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host 'Done.'
|
||||
Write-Host 'done.'
|
||||
}
|
||||
} else {
|
||||
error "$(if ($global) { 'Global' } else { 'Local' }) shim not found: $([char]0x1b)[36m$shimName$([char]0x1b)[31m"
|
||||
Write-Host "ERROR: $(if ($global) { 'Global' } else { 'Local' }) shim not found: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan
|
||||
if (Get-ShimPath $shimName (!$global)) {
|
||||
Write-Host "But a $(if ($global) { 'local' } else { 'global' }) shim exists, run 'scoop shim alter $shimName$(if (!$global) { ' --global' })' to alternate its source."
|
||||
Write-Host "But a $(if ($global) { 'local' } else {'global' }) shim exists, " -NoNewline
|
||||
Write-Host "run 'scoop shim alter $shimName$(if (!$global) { ' --global' })' to alternate its source"
|
||||
exit 2
|
||||
}
|
||||
exit 3
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' 'Select-CurrentVersion' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\system.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # url_filename
|
||||
. "$PSScriptRoot\..\lib\shortcuts.ps1"
|
||||
. "$PSScriptRoot\..\lib\psmodules.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
|
||||
@@ -81,32 +81,13 @@ Describe 'getopt' -Tag 'Scoop' {
|
||||
$opt, $rem, $err = getopt '--long-arg', '--' '' 'long-arg'
|
||||
$err | Should -BeNullOrEmpty
|
||||
$opt.'long-arg' | Should -BeTrue
|
||||
$rem | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'handles remainder args after the option terminator' {
|
||||
$rem[0] | Should -BeNullOrEmpty
|
||||
$opt, $rem, $err = getopt '--long-arg', '--', '-x', '-y' 'xy' 'long-arg'
|
||||
$err | Should -BeNullOrEmpty
|
||||
$opt.'long-arg' | Should -BeTrue
|
||||
$opt.Keys | Should -Not -Contain 'x'
|
||||
$opt.Keys | Should -Not -Contain 'y'
|
||||
$opt.'x' | Should -BeNullOrEmpty
|
||||
$opt.'y' | Should -BeNullOrEmpty
|
||||
$rem[0] | Should -Be '-x'
|
||||
$rem[1] | Should -Be '-y'
|
||||
}
|
||||
|
||||
It 'handles PowerShell stop-parsing token' {
|
||||
$opt, $rem, $err = getopt '--long-arg', '--%' '' 'long-arg'
|
||||
$err | Should -BeNullOrEmpty
|
||||
$opt.'long-arg' | Should -BeTrue
|
||||
$rem | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'handles remainder args after PowerShell stop-parsing token' {
|
||||
$opt, $rem, $err = getopt @('--long-arg', '--%', '--from', 'there', '--to', 'here') '' 'long-arg'
|
||||
$err | Should -BeNullOrEmpty
|
||||
$opt.Keys | Should -Not -Contain 'from'
|
||||
$opt.Keys | Should -Not -Contain 'to'
|
||||
$rem | Should -Be @('--from', 'there', '--to', 'here')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
BeforeAll {
|
||||
. "$PSScriptRoot\..\lib\json.ps1"
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\buckets.ps1"
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
. "$PSScriptRoot\..\lib\autoupdate.ps1"
|
||||
}
|
||||
|
||||
Describe 'JSON parse and beautify' -Tag 'Scoop' {
|
||||
Context 'Parse JSON' {
|
||||
It 'success with valid json' {
|
||||
{ parse_json "$PSScriptRoot\fixtures\manifest\wget.json" } | Should -Not -Throw
|
||||
$parsed = parse_json "$PSScriptRoot\fixtures\manifest\wget.json"
|
||||
$parsed | Should -Not -Be $null
|
||||
}
|
||||
It 'fails with invalid json' {
|
||||
{ parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Throw
|
||||
It 'returns null and warns with invalid json' {
|
||||
Mock warn {}
|
||||
{ parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Not -Throw
|
||||
$parsed = parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json"
|
||||
$parsed | Should -Be $null
|
||||
Should -Invoke -CommandName warn -Times 1
|
||||
}
|
||||
}
|
||||
Context 'Beautify JSON' {
|
||||
@@ -84,3 +94,144 @@ Describe 'Manifest Validator' -Tag 'Validator' {
|
||||
$validator.Errors | Select-Object -Last 1 | Should -Match 'Required properties are missing from object: version\.'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Get-RelativePathCompat' -Tag 'Scoop' {
|
||||
It 'returns relative path for child path' {
|
||||
$from = 'C:\root\bucket'
|
||||
$to = 'C:\root\bucket\foo\bar.json'
|
||||
Get-RelativePathCompat $from $to | Should -Be 'foo\bar.json'
|
||||
}
|
||||
It 'returns original when different drive/scheme' {
|
||||
$from = 'C:\root\bucket'
|
||||
$to = 'D:\other\file.json'
|
||||
Get-RelativePathCompat $from $to | Should -Be $to
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Find-HistoricalManifestInCache' -Tag 'Scoop' {
|
||||
It 'returns $null when sqlite cache disabled' {
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
$result = Find-HistoricalManifestInCache 'foo' 'main' '1.0.0'
|
||||
$result | Should -Be $null
|
||||
}
|
||||
|
||||
It 'returns manifest text and version when cache has exact match' {
|
||||
$tempUM = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock get_config -ParameterFilter { $name -in @('use_sqlite_cache','use_git_history') } { $true }
|
||||
Mock Get-ScoopDBItem {
|
||||
$dt = New-Object System.Data.DataTable
|
||||
[void]$dt.Columns.Add('manifest')
|
||||
$row = $dt.NewRow()
|
||||
$row['manifest'] = '{"version":"1.2.3"}'
|
||||
[void]$dt.Rows.Add($row)
|
||||
Write-Output $dt -NoEnumerate
|
||||
}
|
||||
Mock ensure {}
|
||||
$result = Find-HistoricalManifestInCache 'foo' 'main' '1.2.3'
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result.version | Should -Be '1.2.3'
|
||||
$result.ManifestText | Should -Match '"version":"1.2.3"'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Find-HistoricalManifestInGit' -Tag 'Scoop' {
|
||||
It 'returns $null when git history search disabled' {
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_git_history' } { $false }
|
||||
$result = Find-HistoricalManifestInGit 'foo' 'main' '1.0.0'
|
||||
$result | Should -Be $null
|
||||
}
|
||||
|
||||
It 'returns manifest text on version match' {
|
||||
$bucketRoot = 'C:\b\root'
|
||||
$innerBucket = 'C:\b\root\bucket'
|
||||
$umdir = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_git_history' } { $true }
|
||||
Mock Find-BucketDirectory -ParameterFilter { $Root } { $bucketRoot }
|
||||
Mock Find-BucketDirectory -ParameterFilter { -not $Root } { $innerBucket }
|
||||
Mock Test-Path -ParameterFilter { $Path -eq (Join-Path $bucketRoot '.git') } { $true }
|
||||
Mock Test-Path -ParameterFilter { $Path -eq $innerBucket -and $PathType -eq 'Container' } { $true }
|
||||
# Behavior-oriented mocks: using HEAD should yield a wrong version
|
||||
Mock Invoke-Git -ParameterFilter { $ArgumentList[0] -eq 'show' -and $ArgumentList[1] -like 'HEAD*' } { $global:LASTEXITCODE = 0; return '{"version":"2.0.0"}' }
|
||||
Mock Invoke-Git -ParameterFilter { $ArgumentList[0] -eq 'show' } { $global:LASTEXITCODE = 0; return '{"version":"1.0.0"}' }
|
||||
Mock Invoke-Git -ParameterFilter { $ArgumentList[0] -eq 'log' } { @('abcdef0123456789') }
|
||||
|
||||
$result = Find-HistoricalManifestInGit 'foo' 'main' '1.0.0'
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result.version | Should -Be '1.0.0'
|
||||
$result.ManifestText | Should -Match '"version":"1.0.0"'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'generate_user_manifest (history-aware)' -Tag 'Scoop' {
|
||||
It 'returns manifest_path when versions match' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='1.0.0' }, 'main', $null }
|
||||
Mock manifest_path { 'C:\path\foo.json' }
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be 'C:\path\foo.json'
|
||||
}
|
||||
|
||||
It 'prefers history orchestrator hit (cache) when enabled' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0' }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -in @('use_sqlite_cache','use_git_history') } { $true }
|
||||
Mock Find-HistoricalManifest { @{ path = 'C:\cache\foo.json'; version = '1.0.0'; source='sqlite_exact_match' } }
|
||||
|
||||
Mock info {}
|
||||
Mock warn {}
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be 'C:\cache\foo.json'
|
||||
Should -Invoke -CommandName Find-HistoricalManifest -Times 1
|
||||
|
||||
}
|
||||
|
||||
It 'falls back to git history when cache misses' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0' }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -in @('use_sqlite_cache','use_git_history') } { $true }
|
||||
Mock Find-HistoricalManifest { @{ path = 'C:\git\foo.json'; version = '1.0.0'; source='git_manifest:hash' } }
|
||||
Mock info {}
|
||||
Mock warn {}
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be 'C:\git\foo.json'
|
||||
Should -Invoke -CommandName Find-HistoricalManifest -Times 1
|
||||
|
||||
}
|
||||
|
||||
It 'uses autoupdate when no history found and autoupdate exists' {
|
||||
$umdir = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0'; autoupdate=@{} }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
Mock Find-HistoricalManifest { $null }
|
||||
|
||||
Mock ensure {}
|
||||
Mock usermanifestsdir { $umdir }
|
||||
Mock Invoke-AutoUpdate {}
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be (Join-Path $umdir 'foo.json')
|
||||
}
|
||||
|
||||
It 'on autoupdate failure aborts with concise message' {
|
||||
$umdir = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0'; autoupdate=@{} }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
Mock Find-HistoricalManifest { $null }
|
||||
Mock ensure {}
|
||||
Mock usermanifestsdir { $umdir }
|
||||
Mock Invoke-AutoUpdate { throw 'fail' }
|
||||
Mock warn {}
|
||||
Mock info {}
|
||||
Mock Write-Host {}
|
||||
Mock abort { throw 'aborted' }
|
||||
{ generate_user_manifest 'foo' 'main' '1.0.0' } | Should -Throw
|
||||
}
|
||||
|
||||
It 'aborts when no history and no autoupdate' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0' }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
Mock Find-HistoricalManifest { $null }
|
||||
|
||||
Mock warn {}
|
||||
Mock info {}
|
||||
Mock abort { throw 'aborted' }
|
||||
{ generate_user_manifest 'foo' 'main' '1.0.0' } | Should -Throw
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user