Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
691c233
fix(scoop-download): Fix function `nightly_version` not defined error…
AkariiinMKII Aug 14, 2025
c9f0728
fix(autoupdate): Use origin url to handle urls with fragment in githu…
HUMORCE Aug 20, 2025
ff38652
fix(buckets|scoop-info): Switch git log date format to ISO 8601 to av…
z-Fng Aug 21, 2025
04b7ce7
fix(scoop-version): Fix logic error caused by missing brackets (#6463)
z-Fng Aug 27, 2025
25d3500
fix(core|manifest): Avoid error when searching non-existent 'deprecat…
z-Fng Sep 24, 2025
b592b38
feat(install): Add separator at the end of notes, highlight suggestio…
z-Fng Sep 24, 2025
db8d554
fix(scoop-update): Force sync tags w/ remote branch while scoop updat…
niheaven Sep 26, 2025
4c55e7a
feat(scoop-uninstall): Allow access to $bucket in uninstall scripts (…
abgox Sep 26, 2025
2a63736
fix(scoop-uninstall): Correct `-Global` switch during uninstalling/up…
HUMORCE Sep 26, 2025
ca0506c
refactor(output): Replace raw prints with functions for standardized …
abgox Sep 29, 2025
20a178b
fix(checkver): Allow script to run when URL fetch fails but script ex…
z-Fng Oct 13, 2025
52a035b
fix(path): Trim ending slash when initializing paths (#6501)
niheaven Oct 21, 2025
7966971
fix(schema): add github to the mode of hash (#6534)
abgox Nov 3, 2025
2120c28
fix(buckets): fix the filtering condition when retrieving the number …
abgox Nov 8, 2025
28c51e0
feat(download|scoop-config): Allow disabling automatic fallback to th…
z-Fng Nov 8, 2025
a8d4027
fix(core): Fix the grep parameter in the Invoke-GitLog function (#6407)
abgox Nov 10, 2025
f9c6913
feat(download|scoop-download): Add GitHub issue prompt when the defau…
z-Fng Nov 10, 2025
f0cd3e1
fix(core): Skip NO_JUNCTION logic when $app is 'scoop' in `currentdir…
z-Fng Nov 10, 2025
7e86336
feat(install): Add output for the setting and removal of environment …
abgox Nov 10, 2025
7399945
fix(uninstall): Import url_filename from download.ps1 (#6530)
xrgzs Nov 11, 2025
0e698cc
refactor(output): Combine the separated outputs into a single output …
abgox Nov 11, 2025
f2ee3f9
fix(getopt): Teach getopt to respect the `--%` token (#6477)
refack Nov 13, 2025
874e8e6
fix(checkver): Fix incorrect version returned when script fails witho…
z-Fng Nov 15, 2025
03d831d
fix(install): Don't add to `$Error` when checking for service `cexecs…
o-l-a-v Nov 16, 2025
520188c
feat: Add file lock to prevent concurrent executions
KoltesDigital Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.log
.DS_Store
._.DS_Store
.lock
scoop.sublime-workspace
test/installer/tmp/*
test/tmp/*
Expand Down
44 changes: 40 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
## [Unreleased](https://github.com/ScoopInstaller/Scoop/compare/v0.5.3...develop)

### Features

- **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))
- **download|scoop-download:** Add GitHub issue prompt when the default downloader fails ([#6539](https://github.com/ScoopInstaller/Scoop/issues/6539))
- **download|scoop-config:** Allow disabling automatic fallback to the default downloader when Aria2c download fails ([#6538](https://github.com/ScoopInstaller/Scoop/issues/6538))
- **core:** Add file lock to prevent concurrent executions ([#6557](https://github.com/ScoopInstaller/Scoop/issues/6557))

### Bug Fixes

- **core:** Fix the grep parameter in the `Invoke-GitLog` function ([#6407](https://github.com/ScoopInstaller/Scoop/issues/6407))
- **buckets:** Fix the filtering condition when retrieving the number of manifests ([#6509](https://github.com/ScoopInstaller/Scoop/issues/6509))
- **scoop-download:** Fix function `nightly_version` not defined error ([#6386](https://github.com/ScoopInstaller/Scoop/issues/6386))
- **scoop-uninstall:** Correct `-Global` Switch ([#6454](https://github.com/ScoopInstaller/Scoop/issues/6454))
- **scoop-update:** Force sync tags w/ remote branch while scoop update ([#6439](https://github.com/ScoopInstaller/Scoop/issues/6439))
- **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))
- **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))
- **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))

## [v0.5.3](https://github.com/ScoopInstaller/Scoop/compare/v0.5.2...v0.5.3) - 2025-08-11

### Features

**autoupdate:** GitHub predefined hashes support ([#6416](https://github.com/ScoopInstaller/Scoop/issues/6416), [#6435](https://github.com/ScoopInstaller/Scoop/issues/6435))
- **autoupdate:** GitHub predefined hashes support ([#6416](https://github.com/ScoopInstaller/Scoop/issues/6416), [#6435](https://github.com/ScoopInstaller/Scoop/issues/6435))

### Bug Fixes

- **scoop-download|install|update:** Fallback to default downloader when aria2 fails ([#4292](https://github.com/ScoopInstaller/Scoop/issues/4292))
- **decompress**: `Expand-7zipArchive` only delete temp dir / `$extractDir` if it is empty ([#6092](https://github.com/ScoopInstaller/Scoop/issues/6092))
- **decompress**: Replace deprecated 7ZIPEXTRACT_USE_EXTERNAL config with USE_EXTERNAL_7ZIP ([#6327](https://github.com/ScoopInstaller/Scoop/issues/6327))
- **commands**: Handling broken aliases ([#6141](https://github.com/ScoopInstaller/Scoop/issues/6141))
- **decompress:** `Expand-7zipArchive` only delete temp dir / `$extractDir` if it is empty ([#6092](https://github.com/ScoopInstaller/Scoop/issues/6092))
- **decompress:** Replace deprecated 7ZIPEXTRACT_USE_EXTERNAL config with USE_EXTERNAL_7ZIP ([#6327](https://github.com/ScoopInstaller/Scoop/issues/6327))
- **commands:** Handling broken aliases ([#6141](https://github.com/ScoopInstaller/Scoop/issues/6141))
- **shim:** Do not suppress `stderr`, properly check `wslpath`/`cygpath` command first ([#6114](https://github.com/ScoopInstaller/Scoop/issues/6114))
- **scoop-bucket:** Add missing import for `no_junction` envs ([#6181](https://github.com/ScoopInstaller/Scoop/issues/6181))
- **scoop-uninstall:** Fix uninstaller does not gain Global state ([#6430](https://github.com/ScoopInstaller/Scoop/issues/6430))
Expand Down
19 changes: 15 additions & 4 deletions bin/checkver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -274,18 +274,25 @@ while ($in_progress -gt 0) {
$expected_ver = $json.version
$ver = $Version

$matchesHashtable = @{}

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 (!$script) {
next "$($err.message)`r`nURL $url is not valid"
continue
} else {
# Run script despite URL download failure
Write-Host "$($err.message)`r`nURL $url is not valid. Falling back to checkver.script ..."
}
}

if ($url) {
if ($url -and !$err) {
$ms = New-Object System.IO.MemoryStream
$ms.Write($result, 0, $result.Length)
$ms.Seek(0, 0) | Out-Null
Expand All @@ -300,6 +307,11 @@ while ($in_progress -gt 0) {
$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)
Expand Down Expand Up @@ -358,7 +370,6 @@ 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) {
Expand Down
96 changes: 61 additions & 35 deletions bin/scoop.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,74 @@ Set-StrictMode -Off
. "$PSScriptRoot\..\lib\commands.ps1"
. "$PSScriptRoot\..\lib\help.ps1"

$subCommand = $Args[0]
$lock_file_path = "$PSScriptRoot\..\.lock"
$lock_stream = $null
$lock_show_waiting_message = $true

# for aliases where there's a local function, re-alias so the function takes precedence
$aliases = Get-Alias | Where-Object { $_.Options -notmatch 'ReadOnly|AllScope' } | ForEach-Object { $_.Name }
Get-ChildItem Function: | Where-Object -Property Name -In -Value $aliases | ForEach-Object {
Set-Alias -Name $_.Name -Value Local:$($_.Name) -Scope Script
while (-not $lock_stream) {
try {
$lock_stream = [System.IO.File]::Open($lock_file_path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
} catch [System.IO.IOException] {
# Only deal with ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION.
$error_code = $_.Exception.HResult -band 0xFFFF
if ($error_code -notin 32, 33) {
throw
}

if ($lock_show_waiting_message) {
Write-Host 'Waiting for exclusive access...'
$lock_show_waiting_message = $false
}
Start-Sleep -Seconds 1
}
}

switch ($subCommand) {
({ $subCommand -in @($null, '-h', '--help', '/?') }) {
exec 'help'
try {
$subCommand = $Args[0]

# for aliases where there's a local function, re-alias so the function takes precedence
$aliases = Get-Alias | Where-Object { $_.Options -notmatch 'ReadOnly|AllScope' } | ForEach-Object { $_.Name }
Get-ChildItem Function: | Where-Object -Property Name -In -Value $aliases | ForEach-Object {
Set-Alias -Name $_.Name -Value Local:$($_.Name) -Scope Script
}
({ $subCommand -in @('-v', '--version') }) {
Write-Host 'Current Scoop version:'
if (Test-GitAvailable -and (Test-Path "$PSScriptRoot\..\.git") -and (get_config SCOOP_BRANCH 'master') -ne 'master') {
Invoke-Git -Path "$PSScriptRoot\.." -ArgumentList @('log', 'HEAD', '-1', '--oneline')
} else {
$version = Select-String -Pattern '^## \[(v[\d.]+)\].*?([\d-]+)$' -Path "$PSScriptRoot\..\CHANGELOG.md"
Write-Host $version.Matches.Groups[1].Value -ForegroundColor Cyan -NoNewline
Write-Host " - Released at $($version.Matches.Groups[2].Value)"

switch ($subCommand) {
({ $subCommand -in @($null, '-h', '--help', '/?') }) {
exec 'help'
}
Write-Host ''

Get-LocalBucket | ForEach-Object {
$bucketLoc = Find-BucketDirectory $_ -Root
if (Test-GitAvailable -and (Test-Path "$bucketLoc\.git")) {
Write-Host "'$_' bucket:"
Invoke-Git -Path $bucketLoc -ArgumentList @('log', 'HEAD', '-1', '--oneline')
Write-Host ''
({ $subCommand -in @('-v', '--version') }) {
Write-Host 'Current Scoop version:'
if ((Test-GitAvailable) -and (Test-Path "$PSScriptRoot\..\.git") -and ((get_config SCOOP_BRANCH 'master') -ne 'master')) {
Invoke-Git -Path "$PSScriptRoot\.." -ArgumentList @('--no-pager', 'log', 'HEAD', '-1', '--oneline')
} else {
$version = Select-String -Pattern '^## \[(v[\d.]+)\].*?([\d-]+)$' -Path "$PSScriptRoot\..\CHANGELOG.md"
Write-Host $version.Matches.Groups[1].Value -ForegroundColor Cyan -NoNewline
Write-Host " - Released at $($version.Matches.Groups[2].Value)"
}
Write-Host ''

Get-LocalBucket | ForEach-Object {
$bucketLoc = Find-BucketDirectory $_ -Root
if ((Test-GitAvailable) -and (Test-Path "$bucketLoc\.git")) {
Write-Host "'$_' bucket:"
Invoke-Git -Path $bucketLoc -ArgumentList @('--no-pager', 'log', 'HEAD', '-1', '--oneline')
Write-Host ''
}
}
}
}
({ $subCommand -in (commands) }) {
[string[]]$arguments = $Args | Select-Object -Skip 1
if ($null -ne $arguments -and $arguments[0] -in @('-h', '--help', '/?')) {
exec 'help' @($subCommand)
} else {
exec $subCommand $arguments
({ $subCommand -in (commands) }) {
[string[]]$arguments = $Args | Select-Object -Skip 1
if ($null -ne $arguments -and $arguments[0] -in @('-h', '--help', '/?')) {
exec 'help' @($subCommand)
} else {
exec $subCommand $arguments
}
}
default {
warn "scoop: '$subCommand' isn't a scoop command. See 'scoop help'."
exit 1
}
}
default {
warn "scoop: '$subCommand' isn't a scoop command. See 'scoop help'."
exit 1
}
} finally {
$lock_stream.Dispose()
}
2 changes: 1 addition & 1 deletion bin/uninstall.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function do_uninstall($app, $global) {
$architecture = $install.architecture

Write-Output "Uninstalling '$app'"
Invoke-Installer -Path $dir -Manifest $manifest -ProcessorArchitecture $architecture -Uninstall
Invoke-Installer -Path $dir -Manifest $manifest -ProcessorArchitecture $architecture -Global:$global -Uninstall
rm_shims $app $manifest $global $architecture

# If a junction was used during install, that will have been used
Expand Down
11 changes: 7 additions & 4 deletions lib/autoupdate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
function format_hash([String] $hash) {
$hash = $hash.toLower()

# Workaround for GitHub API:
# `"digest": "sha256:<SHA256_STRING>"`
if ($hash -like 'sha256:*') {
$hash = $hash.Substring(7) # Remove prefix 'sha256:'
}
Expand Down Expand Up @@ -209,13 +211,14 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
$hash = $null

$hashmode = $config.mode
$originurl = strip_fragment $url
$basename = [System.Web.HttpUtility]::UrlDecode((url_remote_filename($url)))

$substitutions = $substitutions.Clone()
$substitutions.Add('$url', (strip_fragment $url))
$substitutions.Add('$baseurl', (strip_filename (strip_fragment $url)).TrimEnd('/'))
$substitutions.Add('$url', $originurl)
$substitutions.Add('$baseurl', (strip_filename $originurl).TrimEnd('/'))
$substitutions.Add('$basename', $basename)
$substitutions.Add('$urlNoExt', (strip_ext (strip_fragment $url)))
$substitutions.Add('$urlNoExt', (strip_ext $originurl))
$substitutions.Add('$basenameNoExt', (strip_ext $basename))

debug $substitutions
Expand Down Expand Up @@ -297,7 +300,7 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
}
'github' {
$hashfile_url = "https://api.github.com/repos/$($matches['owner'])/$($matches['repo'])/releases"
$hash = find_hash_in_json $hashfile_url $substitutions ("$..assets[?(@.browser_download_url == '" + $url + "')].digest")
$hash = find_hash_in_json $hashfile_url $substitutions ("$..assets[?(@.browser_download_url == '" + $originurl + "')].digest")
}
}

Expand Down
6 changes: 3 additions & 3 deletions lib/buckets.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ function list_buckets {
$path = Find-BucketDirectory $_ -Root
if ((Test-Path (Join-Path $path '.git')) -and (Get-Command git -ErrorAction SilentlyContinue)) {
$bucket.Source = Invoke-Git -Path $path -ArgumentList @('config', 'remote.origin.url')
$bucket.Updated = Invoke-Git -Path $path -ArgumentList @('log', '--format=%aD', '-n', '1') | Get-Date
$bucket.Updated = Invoke-Git -Path $path -ArgumentList @('log', '--format=%aI', '-n', '1') | Get-Date
} else {
$bucket.Source = friendly_path $path
$bucket.Updated = (Get-Item "$path\bucket" -ErrorAction SilentlyContinue).LastWriteTime
}
$bucket.Manifests = Get-ChildItem "$path\bucket" -Force -Recurse -ErrorAction SilentlyContinue |
$bucket.Manifests = Get-ChildItem -Path "$path\bucket" -Filter "*.json" -File -Force -Recurse -ErrorAction SilentlyContinue |
Measure-Object | Select-Object -ExpandProperty Count
$buckets += [PSCustomObject]$bucket
}
Expand Down Expand Up @@ -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
Expand Down
25 changes: 16 additions & 9 deletions lib/core.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function load_cfg($file) {
$content = [System.IO.File]::ReadAllLines($file)
return ($content | ConvertFrom-Json -ErrorAction Stop)
} catch {
Write-Host "ERROR loading $file`: $($_.exception.message)"
error "loading $file`: $($_.exception.message)"
}
}

Expand Down Expand Up @@ -276,7 +276,7 @@ function Invoke-GitLog {
}
$Name = "%Cgreen$($Name.PadRight(12, ' ').Substring(0, 12))%Creset "
}
Invoke-Git -Path $Path -ArgumentList @('--no-pager', 'log', '--color', '--no-decorate', "--grep='^(chore)'", '--invert-grep', '--abbrev=12', "--format=tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s $Name%C(cyan)%cr%Creset", "$CommitHash..HEAD")
Invoke-Git -Path $Path -ArgumentList @('--no-pager', 'log', '--color', '--no-decorate', '--grep=^(chore)', '--invert-grep', '--abbrev=12', "--format=tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s $Name%C(cyan)%cr%Creset", "$CommitHash..HEAD")
}
}

Expand Down Expand Up @@ -353,7 +353,7 @@ function appdir($app, $global) { "$(appsdir $global)\$app" }
function versiondir($app, $version, $global) { "$(appdir $app $global)\$version" }

function currentdir($app, $global) {
if (get_config NO_JUNCTION) {
if ((get_config NO_JUNCTION) -and ($app -ne 'scoop')) {
$version = Select-CurrentVersion -App $app -Global:$global
} else {
$version = 'current'
Expand Down Expand Up @@ -555,7 +555,7 @@ function app_status($app, $global) {
$status.hold = ($install_info.hold -eq $true)

$deprecated_dir = (Find-BucketDirectory -Name $install_info.bucket -Root) + "\deprecated"
$status.deprecated = (Get-ChildItem $deprecated_dir -Filter "$(sanitary_path $app).json" -Recurse).FullName
$status.deprecated = (Get-ChildItem $deprecated_dir -Filter "$(sanitary_path $app).json" -Recurse -ErrorAction Ignore).FullName

$manifest = manifest $app $install_info.bucket $install_info.url
$status.removed = (!$manifest)
Expand Down Expand Up @@ -622,7 +622,14 @@ function Get-AbsolutePath {
$Path
)
process {
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
$resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
if ($resolvedPath -match '[\\/]$') {
$root = [System.IO.Path]::GetPathRoot($resolvedPath)
if ($resolvedPath -ine $root) {
$resolvedPath = $resolvedPath.TrimEnd([char[]]@('\', '/'))
}
}
return $resolvedPath
}
}

Expand Down Expand Up @@ -743,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
Expand All @@ -762,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
}
Expand Down
Loading
Loading