mirror of
https://github.com/ScoopInstaller/Scoop.git
synced 2025-10-30 06:07:56 +00:00
* fix (decompress): `Expand-7zipArchive` only delete temp dir / `$extractDir` if it is empty (#6092) Co-authored-by: Hsiao-nan Cheung <niheaven@gmail.com> * refactor(download): Move download-related functions to 'download.ps1' (#6095) * fix(download): Fallback to default downloader when aria2 fails (#4292) * fix(commands): Handling broken aliases (#6141) * fix(shim): properly check `wslpath`/`cygpath` command first (#6114) Co-authored-by: Hsiao-nan Cheung <niheaven@gmail.com> * fix(scoop-bucket): Add missing import for `no_junction` envs (#6181) Signed-off-by: Chawye Hsu <su+git@chawyehsu.com> * docs(chglog): Update to 0.5.3 (#6258) * perf(shim): Update kiennq-shim to v3.1.2 (#6261) * fix(decompress): Replace deprecated 7ZIPEXTRACT_USE_EXTERNAL config (#6327) Co-authored-by: Hsiao-nan Cheung <niheaven@gmail.com> * fix(scoop-uninstall): Fix uninstaller does not gain Global state (#6430) * global arg * changelog * refactor(Get-Manifest): Select actual source for manifest (#6142) * first step * Revert "first step" This reverts commitc5907c3e25. * refactor(Get-Manifest): Select actual source for installed manifest * rework sub-commands, `scoop-depends` is NOT working at this stage * URI manifest * opt * deprecated manifest * source of manifests * source of manifest pt2 - Mark URI(path/URL/UNC/etc.) query as standalone manifest - Drop `installed` and `available update` items for [query] and [installed] are different sources. * remove variable preventing I forget it * scoop-info: fix source of manifest on bucket * fix `scoop-depends` * Fix Standalone and Source detection * fix global install * Fix scoop-cat, scoop-home - Query for remote manifest * scoop-list: info +deprecated * manifest: Fix first selected manifest * gramma.. * Fix61b3259* length * fix(scoop-depends-tests): Mocking `USE_EXTERNAL_7ZIP` as $false (#6431) * fix(scoop-depends-tests): Mocking `USE_EXTERNAL_7ZIP` as $false to avoding error when it is $true * CHANGELOG * feat(autoupdate): GitHub predefined hashes support (#6416) * feat(autoupdate): predefined hash case for GitHub - Remove `sha256:` prefix in `format_hash()` - Add GitHub support in `get_hash_for_app()` Close #6381 * doc(chglog): GitHub auto hash update * fix(autoupdate): remove prefix only * docs(CHANGELOG): Update to 0.5.3 (#6432) * docs(CHANGELOG): Update to 0.5.3 * 6416 --------- Signed-off-by: Chawye Hsu <su+git@chawyehsu.com> Co-authored-by: Olav Rønnestad Birkeland <6450056+o-l-a-v@users.noreply.github.com> Co-authored-by: kiennq <kien.n.quang@gmail.com> Co-authored-by: HUMORCE <humorce@outlook.com> Co-authored-by: Ryan <sitiom@proton.me> Co-authored-by: Chawye Hsu <su+git@chawyehsu.com> Co-authored-by: Bassel Rachid <101208715+basselworkforce@users.noreply.github.com> Co-authored-by: Wordless Echo <wordless@echo.moe>
421 lines
13 KiB
PowerShell
421 lines
13 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Check manifest for a newer version.
|
|
.DESCRIPTION
|
|
Checks websites for newer versions using an (optional) regular expression defined in the manifest.
|
|
.PARAMETER App
|
|
Manifest name to search.
|
|
Placeholders are supported.
|
|
.PARAMETER Dir
|
|
Where to search for manifest(s).
|
|
.PARAMETER Update
|
|
Update given manifest
|
|
.PARAMETER ForceUpdate
|
|
Update given manifest(s) even when there is no new version.
|
|
Useful for hash updates.
|
|
.PARAMETER SkipUpdated
|
|
Updated manifests will not be shown.
|
|
.PARAMETER Version
|
|
Update manifest to specific version.
|
|
.PARAMETER ThrowError
|
|
Throw error as exception instead of just printing it.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1
|
|
Check all manifest inside default directory.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 -SkipUpdated
|
|
Check all manifest inside default directory (list only outdated manifests).
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 -Update
|
|
Check all manifests and update All outdated manifests.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 APP
|
|
Check manifest APP.json inside default directory.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 APP -Update
|
|
Check manifest APP.json and update, if there is newer version.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 APP -ForceUpdate
|
|
Check manifest APP.json and update, even if there is no new version.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 APP -Update -Version VER
|
|
Check manifest APP.json and update, using version VER
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 APP DIR
|
|
Check manifest APP.json inside ./DIR directory.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 -Dir DIR
|
|
Check all manifests inside ./DIR directory.
|
|
.EXAMPLE
|
|
PS BUCKETROOT > .\bin\checkver.ps1 APP DIR -Update
|
|
Check manifest APP.json inside ./DIR directory and update if there is newer version.
|
|
#>
|
|
param(
|
|
[String] $App = '*',
|
|
[ValidateScript( {
|
|
if (!(Test-Path $_ -Type Container)) {
|
|
throw "$_ is not a directory!"
|
|
} else {
|
|
$true
|
|
}
|
|
})]
|
|
[String] $Dir,
|
|
[Switch] $Update,
|
|
[Switch] $ForceUpdate,
|
|
[Switch] $SkipUpdated,
|
|
[String] $Version = '',
|
|
[Switch] $ThrowError
|
|
)
|
|
|
|
. "$PSScriptRoot\..\lib\core.ps1"
|
|
. "$PSScriptRoot\..\lib\autoupdate.ps1"
|
|
. "$PSScriptRoot\..\lib\manifest.ps1"
|
|
. "$PSScriptRoot\..\lib\buckets.ps1"
|
|
. "$PSScriptRoot\..\lib\json.ps1"
|
|
. "$PSScriptRoot\..\lib\versions.ps1"
|
|
. "$PSScriptRoot\..\lib\download.ps1"
|
|
|
|
if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) {
|
|
$Dir = Split-Path $App
|
|
$files = Get-ChildItem $Dir -Filter (Split-Path $App -Leaf)
|
|
} elseif ($Dir) {
|
|
$Dir = Convert-Path $Dir
|
|
$files = Get-ChildItem $Dir -Filter "$App.json" -Recurse
|
|
} else {
|
|
throw "'-Dir' parameter required if '-App' is not a filepath!"
|
|
}
|
|
|
|
$GitHubToken = Get-GitHubToken
|
|
|
|
# don't use $Version with $App = '*'
|
|
if ($App -eq '*' -and $Version -ne '') {
|
|
throw "Don't use '-Version' with '-App *'!"
|
|
}
|
|
|
|
# get apps to check
|
|
$Queue = @()
|
|
$json = ''
|
|
$files | ForEach-Object {
|
|
$file = $_.FullName
|
|
$json = parse_json $file
|
|
if ($json.checkver) {
|
|
$Queue += , @($_.BaseName, $json, $file)
|
|
}
|
|
}
|
|
|
|
# clear any existing events
|
|
Get-Event | Remove-Event
|
|
Get-EventSubscriber | Unregister-Event
|
|
|
|
# start all downloads
|
|
$Queue | ForEach-Object {
|
|
$name, $json, $file = $_
|
|
|
|
$substitutions = Get-VersionSubstitution $json.version # 'autoupdate.ps1'
|
|
|
|
$wc = New-Object Net.Webclient
|
|
if ($json.checkver.useragent) {
|
|
$wc.Headers.Add('User-Agent', (substitute $json.checkver.useragent $substitutions))
|
|
} else {
|
|
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
|
}
|
|
Register-ObjectEvent $wc downloadDataCompleted -ErrorAction Stop | Out-Null
|
|
|
|
# Not Specified
|
|
if ($json.checkver.url) {
|
|
$url = $json.checkver.url
|
|
} else {
|
|
$url = $json.homepage
|
|
}
|
|
|
|
if ($json.checkver.re) {
|
|
$regex = $json.checkver.re
|
|
} elseif ($json.checkver.regex) {
|
|
$regex = $json.checkver.regex
|
|
} else {
|
|
$regex = ''
|
|
}
|
|
|
|
$jsonpath = ''
|
|
$xpath = ''
|
|
$replace = ''
|
|
$useGithubAPI = $false
|
|
|
|
# GitHub
|
|
if ($regex) {
|
|
$githubRegex = $regex
|
|
} else {
|
|
$githubRegex = '/releases/tag/(?:v|V)?([\d.]+)'
|
|
}
|
|
if ($json.checkver -eq 'github') {
|
|
if (!$json.homepage.StartsWith('https://github.com/')) {
|
|
error "$name checkver expects the homepage to be a github repository"
|
|
}
|
|
$url = $json.homepage.TrimEnd('/') + '/releases/latest'
|
|
$regex = $githubRegex
|
|
$useGithubAPI = $true
|
|
}
|
|
|
|
if ($json.checkver.github) {
|
|
$url = $json.checkver.github.TrimEnd('/') + '/releases/latest'
|
|
$regex = $githubRegex
|
|
if ($json.checkver.PSObject.Properties.Count -eq 1) { $useGithubAPI = $true }
|
|
}
|
|
|
|
# SourceForge
|
|
if ($regex) {
|
|
$sourceforgeRegex = $regex
|
|
} else {
|
|
$sourceforgeRegex = '(?!\.)([\d.]+)(?<=\d)'
|
|
}
|
|
if ($json.checkver -eq 'sourceforge') {
|
|
if ($json.homepage -match '//(sourceforge|sf)\.net/projects/(?<project>[^/]+)(/files/(?<path>[^/]+))?|//(?<project>[^.]+)\.(sourceforge\.(net|io)|sf\.net)') {
|
|
$project = $Matches['project']
|
|
$path = $Matches['path']
|
|
} else {
|
|
$project = strip_ext $name
|
|
}
|
|
$url = "https://sourceforge.net/projects/$project/rss"
|
|
if ($path) {
|
|
$url = $url + '?path=/' + $path.TrimStart('/')
|
|
}
|
|
$regex = "CDATA\[/$path/.*?$sourceforgeRegex.*?\]".Replace('//', '/')
|
|
}
|
|
if ($json.checkver.sourceforge) {
|
|
if ($json.checkver.sourceforge -is [System.String] -and $json.checkver.sourceforge -match '(?<project>[\w-]*)(/(?<path>.*))?') {
|
|
$project = $Matches['project']
|
|
$path = $Matches['path']
|
|
} else {
|
|
$project = $json.checkver.sourceforge.project
|
|
$path = $json.checkver.sourceforge.path
|
|
}
|
|
$url = "https://sourceforge.net/projects/$project/rss"
|
|
if ($path) {
|
|
$url = $url + '?path=/' + $path.TrimStart('/')
|
|
}
|
|
$regex = "CDATA\[/$path/.*?$sourceforgeRegex.*?\]".Replace('//', '/')
|
|
}
|
|
|
|
if ($json.checkver.jp) {
|
|
$jsonpath = $json.checkver.jp
|
|
}
|
|
if ($json.checkver.jsonpath) {
|
|
$jsonpath = $json.checkver.jsonpath
|
|
}
|
|
if ($json.checkver.xpath) {
|
|
$xpath = $json.checkver.xpath
|
|
}
|
|
|
|
if ($json.checkver.replace -is [System.String]) { # If `checkver` is [System.String], it has a method called `Replace`
|
|
$replace = $json.checkver.replace
|
|
}
|
|
|
|
if (!$jsonpath -and !$regex -and !$xpath) {
|
|
$regex = $json.checkver
|
|
}
|
|
|
|
$reverse = $json.checkver.reverse -and $json.checkver.reverse -eq 'true'
|
|
|
|
if ($url -like '*api.github.com/*') { $useGithubAPI = $true }
|
|
|
|
if ($useGithubAPI -and ($null -ne $GitHubToken)) {
|
|
$url = $url -replace '//(www\.)?github.com/', '//api.github.com/repos/'
|
|
$wc.Headers.Add('Authorization', "token $GitHubToken")
|
|
}
|
|
|
|
$url = substitute $url $substitutions
|
|
|
|
$state = New-Object psobject @{
|
|
app = $name
|
|
file = $file
|
|
url = $url
|
|
regex = $regex
|
|
json = $json
|
|
jsonpath = $jsonpath
|
|
xpath = $xpath
|
|
reverse = $reverse
|
|
replace = $replace
|
|
}
|
|
|
|
get_config PRIVATE_HOSTS | Where-Object { $_ -ne $null -and $url -match $_.match } | ForEach-Object {
|
|
(ConvertFrom-StringData -StringData $_.Headers).GetEnumerator() | ForEach-Object {
|
|
$wc.Headers[$_.Key] = $_.Value
|
|
}
|
|
}
|
|
|
|
$wc.Headers.Add('Referer', (strip_filename $url))
|
|
$wc.DownloadDataAsync($url, $state)
|
|
}
|
|
|
|
function next($er) {
|
|
Write-Host "$App`: " -NoNewline
|
|
Write-Host $er -ForegroundColor DarkRed
|
|
}
|
|
|
|
# wait for all to complete
|
|
$in_progress = $Queue.length
|
|
while ($in_progress -gt 0) {
|
|
$ev = Wait-Event
|
|
Remove-Event $ev.SourceIdentifier
|
|
$in_progress--
|
|
|
|
$state = $ev.SourceEventArgs.UserState
|
|
$result = $ev.SourceEventArgs.Result
|
|
$app = $state.app
|
|
$file = $state.file
|
|
$json = $state.json
|
|
$url = $state.url
|
|
$regexp = $state.regex
|
|
$jsonpath = $state.jsonpath
|
|
$xpath = $state.xpath
|
|
$script = $json.checkver.script
|
|
$reverse = $state.reverse
|
|
$replace = $state.replace
|
|
$expected_ver = $json.version
|
|
$ver = $Version
|
|
|
|
if (!$ver) {
|
|
if (!$regexp -and $replace) {
|
|
next "'replace' requires 're' or 'regex'"
|
|
continue
|
|
}
|
|
$err = $ev.SourceEventArgs.Error
|
|
if ($err) {
|
|
next "$($err.message)`r`nURL $url is not valid"
|
|
continue
|
|
}
|
|
|
|
if ($url) {
|
|
$ms = New-Object System.IO.MemoryStream
|
|
$ms.Write($result, 0, $result.Length)
|
|
$ms.Seek(0, 0) | Out-Null
|
|
if ($result[0] -eq 0x1F -and $result[1] -eq 0x8B) {
|
|
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
|
|
}
|
|
$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 ($jsonpath) {
|
|
# Return only a single value if regex is absent
|
|
$noregex = [String]::IsNullOrEmpty($regexp)
|
|
# If reverse is ON and regex is ON,
|
|
# Then reverse would have no effect because regex handles reverse
|
|
# on its own
|
|
# So in this case we have to disable reverse
|
|
$ver = json_path $page $jsonpath $null ($reverse -and $noregex) $noregex
|
|
if (!$ver) {
|
|
$ver = json_path_legacy $page $jsonpath
|
|
}
|
|
if (!$ver) {
|
|
next "couldn't find '$jsonpath' in $source"
|
|
continue
|
|
}
|
|
}
|
|
|
|
if ($xpath) {
|
|
$xml = [xml]$page
|
|
# Find all `significant namespace declarations` from the XML file
|
|
$nsList = $xml.SelectNodes("//namespace::*[not(. = ../../namespace::*)]")
|
|
# Then add them into the NamespaceManager
|
|
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
|
|
$nsList | ForEach-Object {
|
|
if ($_.LocalName -eq 'xmlns') {
|
|
$nsmgr.AddNamespace('ns', $_.Value)
|
|
$xpath = $xpath -replace '/([^:/]+)((?=/)|(?=$))', '/ns:$1'
|
|
} else {
|
|
$nsmgr.AddNamespace($_.LocalName, $_.Value)
|
|
}
|
|
}
|
|
# Getting version from XML, using XPath
|
|
$ver = $xml.SelectSingleNode($xpath, $nsmgr).'#text'
|
|
if (!$ver) {
|
|
next "couldn't find '$($xpath -replace 'ns:', '')' in $source"
|
|
continue
|
|
}
|
|
}
|
|
|
|
if ($jsonpath -and $regexp) {
|
|
$page = $ver
|
|
$ver = ''
|
|
}
|
|
|
|
if ($xpath -and $regexp) {
|
|
$page = $ver
|
|
$ver = ''
|
|
}
|
|
|
|
if ($regexp) {
|
|
$re = New-Object System.Text.RegularExpressions.Regex($regexp)
|
|
if ($reverse) {
|
|
$match = $re.Matches($page) | Select-Object -Last 1
|
|
} else {
|
|
$match = $re.Matches($page) | Select-Object -First 1
|
|
}
|
|
|
|
if ($match -and $match.Success) {
|
|
$matchesHashtable = @{}
|
|
$re.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) }
|
|
$ver = $matchesHashtable['1']
|
|
if ($replace) {
|
|
$ver = $re.Replace($match.Value, $replace)
|
|
}
|
|
if (!$ver) {
|
|
$ver = $matchesHashtable['version']
|
|
}
|
|
} else {
|
|
next "couldn't match '$regexp' in $source"
|
|
continue
|
|
}
|
|
}
|
|
|
|
if (!$ver) {
|
|
next "couldn't find new version in $source"
|
|
continue
|
|
}
|
|
}
|
|
|
|
# Skip actual only if versions are same and there is no -f
|
|
if (($ver -eq $expected_ver) -and !$ForceUpdate -and $SkipUpdated) { continue }
|
|
|
|
Write-Host "$app`: " -NoNewline
|
|
|
|
# version hasn't changed (step over if forced update)
|
|
if ($ver -eq $expected_ver -and !$ForceUpdate) {
|
|
Write-Host $ver -ForegroundColor DarkGreen
|
|
continue
|
|
}
|
|
|
|
Write-Host $ver -ForegroundColor DarkRed -NoNewline
|
|
Write-Host " (scoop version is $expected_ver)" -NoNewline
|
|
$update_available = (Compare-Version -ReferenceVersion $ver -DifferenceVersion $expected_ver) -ne 0
|
|
|
|
if ($json.autoupdate -and $update_available) {
|
|
Write-Host ' autoupdate available' -ForegroundColor Cyan
|
|
} else {
|
|
Write-Host ''
|
|
}
|
|
|
|
# forcing an update implies updating, right?
|
|
if ($ForceUpdate) { $Update = $true }
|
|
|
|
if ($Update -and $json.autoupdate) {
|
|
if ($ForceUpdate) {
|
|
Write-Host 'Forcing autoupdate!' -ForegroundColor DarkMagenta
|
|
}
|
|
try {
|
|
Invoke-AutoUpdate $app $file $json $ver $matchesHashtable # 'autoupdate.ps1'
|
|
} catch {
|
|
if ($ThrowError) {
|
|
throw $_
|
|
} else {
|
|
error $_.Exception.Message
|
|
}
|
|
}
|
|
}
|
|
}
|