Add Vivaldi as a Fleet-maintained app - #41552
Conversation
Adds Vivaldi browser as a Fleet Maintained App for macOS (darwin only). Windows is not included because Vivaldi stable is absent from the microsoft/winget-pkgs repository (only the Snapshot edition is in WinGet). macOS uses the enricher pattern to override Homebrew's tar.xz download with Vivaldi's direct DMG URL, keeping installer_format as "dmg" so the standard hdiutil-based install script is generated unchanged.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #41552 +/- ##
==========================================
- Coverage 67.19% 67.18% -0.01%
==========================================
Files 3621 3630 +9
Lines 229297 229333 +36
Branches 11945 11751 -194
==========================================
+ Hits 154069 154086 +17
- Misses 61370 61397 +27
+ Partials 13858 13850 -8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This is not accurate. Ex: https://github.com/microsoft/winget-pkgs/blob/master/manifests/v/Vivaldi/Vivaldi/7.8.3925.81/Vivaldi.Vivaldi.installer.yaml Moving back to draft. This one should probably be redone. |
…on ASCII Add Vivaldi browser as a fleet-maintained app for both macOS (homebrew, tar.xz format) and Windows (winget, exe installer with custom install and uninstall scripts). Add tar.xz extraction support to the homebrew ingester scripts. Add frontend icon and app-icon asset. Fix non-ASCII characters in apps.json: replace curly apostrophe (U+2019) with straight apostrophe in Adobe Creative Cloud description, and replace em-dashes (U+2014) with hyphens in Airtame and Cursor descriptions.
… scheduled task pattern
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install Script (no changes) ===
=== Uninstall // 532c3250 -> dd74a7d3 ===
--- /tmp/old.cCVOTg 2026-03-16 19:40:07.456048918 +0000
+++ /tmp/new.tfRLSo 2026-03-16 19:40:07.457048921 +0000
@@ -1,173 +1,58 @@
-$softwareName = "Vivaldi"
+# Uninstall Vivaldi (user-scoped Chromium-based browser)
-# Script to uninstall software as the current logged-in user.
-$userScript = @'
+$displayName = "Vivaldi"
-# Define acceptable/expected exit codes
-$ExpectedExitCodes = @(0, 19)
-
-$softwareName = "Vivaldi"
-
-# Using the exact software name here is recommended to avoid
-# uninstalling unintended software.
-$softwareNameLike = "*$softwareName*"
-
-# Some uninstallers require additional flags to run silently.
-# Each uninstaller might use a different argument (usually it's "/S" or "/s")
-$uninstallArgs = "--vivaldi-silent --force-uninstall"
-
-$uninstallCommand = ""
-$exitCode = 0
-
-try {
-
-$userKey = `
- 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
-[array]$uninstallKeys = Get-ChildItem `
- -Path @($userKey) `
- -ErrorAction SilentlyContinue |
- ForEach-Object { Get-ItemProperty $_.PSPath }
-
-$foundUninstaller = $false
-foreach ($key in $uninstallKeys) {
- # If needed, add -notlike to the comparison to exclude certain similar
- # software
- if ($key.DisplayName -like $softwareNameLike) {
- $foundUninstaller = $true
- # Get the uninstall command. Some uninstallers do not include
- # 'QuietUninstallString' and require a flag to run silently.
- $uninstallCommand = if ($key.QuietUninstallString) {
- $key.QuietUninstallString
- } else {
- $key.UninstallString
- }
-
- # The uninstall command may contain command and args, like:
- # "C:\Program Files\Software\uninstall.exe" --uninstall --silent
- # Split the command and args
- $splitArgs = $uninstallCommand.Split('"')
- if ($splitArgs.Length -gt 1) {
- if ($splitArgs.Length -eq 3) {
- $uninstallArgs = "$( $splitArgs[2] ) $uninstallArgs".Trim()
- } elseif ($splitArgs.Length -gt 3) {
- Throw `
- "Uninstall command contains multiple quoted strings. " +
- "Please update the uninstall script.`n" +
- "Uninstall command: $uninstallCommand"
- }
- $uninstallCommand = $splitArgs[1]
- }
- Write-Host "Uninstall command: $uninstallCommand"
- Write-Host "Uninstall args: $uninstallArgs"
-
- $processOptions = @{
- FilePath = $uninstallCommand
- PassThru = $true
- Wait = $true
- }
- if ($uninstallArgs -ne '') {
- $processOptions.ArgumentList = "$uninstallArgs"
- }
-
- # Start the process and track the exit code
- $process = Start-Process @processOptions
- $exitCode = $process.ExitCode
-
- # Prints the exit code
- Write-Host "Uninstall exit code: $exitCode"
- # Exit the loop once the software is found and uninstalled.
- break
- }
+$paths = @(
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+)
+
+$uninstall = $null
+foreach ($p in $paths) {
+ $items = Get-ItemProperty "$p\*" -ErrorAction SilentlyContinue | Where-Object {
+ $_.DisplayName -and $_.DisplayName -eq $displayName
+ }
+ if ($items) { $uninstall = $items | Select-Object -First 1; break }
}
-if (-not $foundUninstaller) {
- Write-Host "Uninstaller for '$softwareName' not found."
- $exitCode = 1
+if (-not $uninstall -or (-not $uninstall.UninstallString -and -not $uninstall.QuietUninstallString)) {
+ Write-Host "Uninstall entry not found for '$displayName'"
+ Exit 1
}
-} catch {
- Write-Host "Error: $_"
- $exitCode = 1
-}
+# Kill any running Vivaldi processes before uninstalling
+Stop-Process -Name "vivaldi" -Force -ErrorAction SilentlyContinue
+Start-Sleep -Seconds 2
-# Treat acceptable exit codes as success
-if ($ExpectedExitCodes -contains $exitCode) {
- Exit 0
+$uninstallCommand = if ($uninstall.QuietUninstallString) {
+ $uninstall.QuietUninstallString
} else {
- Exit $exitCode
+ $uninstall.UninstallString
}
-'@
-$exitCode = 0
+# Parse quoted executable from any trailing args in the registry string
+$splitArgs = $uninstallCommand.Split('"')
+$exe = $splitArgs[1]
+$existingArgs = if ($splitArgs.Length -eq 3) { $splitArgs[2].Trim() } else { "" }
-# Create a script in a public folder so that it can be accessed by all users.
-$uninstallScriptPath = "${env:PUBLIC}/uninstall-$softwareName.ps1"
-$taskName = "fleet-uninstall-$softwareName"
-try {
- Set-Content -Path $uninstallScriptPath -Value $userScript -Force
+# Chromium-based uninstaller flags
+$uninstallArgs = "$existingArgs --uninstall --force-uninstall".Trim()
- # Task properties. The task will be started by the logged in user
- $action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
- -Argument "$uninstallScriptPath"
- $trigger = New-ScheduledTaskTrigger -AtLogOn
- $userName = (Get-CimInstance Win32_Process -Filter 'name = "explorer.exe"' | Invoke-CimMethod -MethodName getowner).User
- $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
-
- # Create a task object with the properties defined above
- $task = New-ScheduledTask -Action $action -Trigger $trigger `
- -Settings $settings
-
- # Register the task
- Register-ScheduledTask "$taskName" -InputObject $task -User "$userName"
-
- # keep track of the start time to cancel if taking too long to start
- $startDate = Get-Date
-
- # Start the task now that it is ready
- Start-ScheduledTask -TaskName "$taskName" -TaskPath "\"
-
- # Wait for the task to be running
- $state = (Get-ScheduledTask -TaskName "$taskName").State
- Write-Host "ScheduledTask is '$state'"
-
- while ($state -ne "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting to uninstall..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 1
- $state = (Get-ScheduledTask -TaskName "$taskName").State
- }
-
- # Wait for the task to be done
- $state = (Get-ScheduledTask -TaskName "$taskName").State
- while ($state -eq "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting for .exe to complete..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 10
- $state = (Get-ScheduledTask -TaskName "$taskName").State
- }
+Write-Host "Uninstall command: $exe"
+Write-Host "Uninstall args: $uninstallArgs"
-} catch {
- Write-Host "Error: $_"
- $exitCode = 1
-} finally {
- # Remove task
- Write-Host "Removing ScheduledTask: $taskName."
- Unregister-ScheduledTask -TaskName "$taskName" -Confirm:$false
+try {
+ $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs -NoNewWindow -PassThru -Wait
+ $exitCode = $process.ExitCode
+ Write-Host "Uninstall exit code: $exitCode"
- # Remove user script
- Remove-Item -Path $uninstallScriptPath -Force
+ # Chromium uninstallers return 19 on success
+ if ($exitCode -eq 0 -or $exitCode -eq 19) {
+ Exit 0
+ }
+ Exit $exitCode
+} catch {
+ Write-Host "Error running uninstaller: $_"
+ Exit 1
}
-
-Exit $exitCode |
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // ab030635 -> 81b0529a ===
--- /tmp/old.eI86eK 2026-03-16 19:44:15.518152475 +0000
+++ /tmp/new.1r5rLm 2026-03-16 19:44:15.518152475 +0000
@@ -1,78 +1,5 @@
-$exeFilePath = "${env:INSTALLER_PATH}"
-
-$exitCode = 0
-
-try {
-
-# Copy the installer to a public folder so that all can access it
-# users
-$exeFilename = Split-Path $exeFilePath -leaf
-Copy-Item -Path $exeFilePath -Destination "${env:PUBLIC}" -Force
-$exeFilePath = "${env:PUBLIC}\$exeFilename"
-
-# Task properties. The task will be started by the logged in user
-$action = New-ScheduledTaskAction -Execute "$exeFilePath" `
- -Argument "--vivaldi-silent --do-not-launch-chrome"
-$trigger = New-ScheduledTaskTrigger -AtLogOn
-$userName = (Get-CimInstance Win32_Process -Filter 'name = "explorer.exe"' | Invoke-CimMethod -MethodName getowner).User
-$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
-
-# Create a task object with the properties defined above
-$task = New-ScheduledTask -Action $action -Trigger $trigger `
- -Settings $settings
-
-# Register the task
-$taskName = "fleet-install-$exeFilename"
-Register-ScheduledTask "$taskName" -InputObject $task -User "$userName"
-
-# keep track of the start time to cancel if taking too long to start
-$startDate = Get-Date
-
-# Start the task now that it is ready
-Start-ScheduledTask -TaskName "$taskName" -TaskPath "\"
-
-# Wait for the task to be running
-$state = (Get-ScheduledTask -TaskName "$taskName").State
-Write-Host "ScheduledTask is '$state'"
-
-while ($state -ne "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting to run .exe..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 1
- $state = (Get-ScheduledTask -TaskName "$taskName").State
-}
-
-# Wait for the task to be done
-$state = (Get-ScheduledTask -TaskName "$taskName").State
-while ($state -eq "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting for .exe to complete..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 10
- $state = (Get-ScheduledTask -TaskName "$taskName").State
-}
-
-# Remove task
-Write-Host "Removing ScheduledTask: $taskName."
-Unregister-ScheduledTask -TaskName "$taskName" -Confirm:$false
-
-} catch {
- Write-Host "Error: $_"
- $exitCode = 1
-} finally {
- # Remove installer
- Remove-Item -Path $exeFilePath -Force
-}
-
-Exit $exitCode
+# Install Vivaldi silently (user-scoped Chromium-based browser)
+$process = Start-Process -FilePath $env:INSTALLER_PATH `
+ -ArgumentList "--vivaldi-silent --do-not-launch-chrome" `
+ -NoNewWindow -PassThru -Wait
+Exit $process.ExitCode
=== Uninstall Script (no changes) === |
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // ab030635 -> 81b0529a ===
--- /tmp/old.607ZUq 2026-03-16 19:51:25.663634389 +0000
+++ /tmp/new.rLLKxh 2026-03-16 19:51:25.663634389 +0000
@@ -1,78 +1,5 @@
-$exeFilePath = "${env:INSTALLER_PATH}"
-
-$exitCode = 0
-
-try {
-
-# Copy the installer to a public folder so that all can access it
-# users
-$exeFilename = Split-Path $exeFilePath -leaf
-Copy-Item -Path $exeFilePath -Destination "${env:PUBLIC}" -Force
-$exeFilePath = "${env:PUBLIC}\$exeFilename"
-
-# Task properties. The task will be started by the logged in user
-$action = New-ScheduledTaskAction -Execute "$exeFilePath" `
- -Argument "--vivaldi-silent --do-not-launch-chrome"
-$trigger = New-ScheduledTaskTrigger -AtLogOn
-$userName = (Get-CimInstance Win32_Process -Filter 'name = "explorer.exe"' | Invoke-CimMethod -MethodName getowner).User
-$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
-
-# Create a task object with the properties defined above
-$task = New-ScheduledTask -Action $action -Trigger $trigger `
- -Settings $settings
-
-# Register the task
-$taskName = "fleet-install-$exeFilename"
-Register-ScheduledTask "$taskName" -InputObject $task -User "$userName"
-
-# keep track of the start time to cancel if taking too long to start
-$startDate = Get-Date
-
-# Start the task now that it is ready
-Start-ScheduledTask -TaskName "$taskName" -TaskPath "\"
-
-# Wait for the task to be running
-$state = (Get-ScheduledTask -TaskName "$taskName").State
-Write-Host "ScheduledTask is '$state'"
-
-while ($state -ne "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting to run .exe..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 1
- $state = (Get-ScheduledTask -TaskName "$taskName").State
-}
-
-# Wait for the task to be done
-$state = (Get-ScheduledTask -TaskName "$taskName").State
-while ($state -eq "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting for .exe to complete..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 10
- $state = (Get-ScheduledTask -TaskName "$taskName").State
-}
-
-# Remove task
-Write-Host "Removing ScheduledTask: $taskName."
-Unregister-ScheduledTask -TaskName "$taskName" -Confirm:$false
-
-} catch {
- Write-Host "Error: $_"
- $exitCode = 1
-} finally {
- # Remove installer
- Remove-Item -Path $exeFilePath -Force
-}
-
-Exit $exitCode
+# Install Vivaldi silently (user-scoped Chromium-based browser)
+$process = Start-Process -FilePath $env:INSTALLER_PATH `
+ -ArgumentList "--vivaldi-silent --do-not-launch-chrome" `
+ -NoNewWindow -PassThru -Wait
+Exit $process.ExitCode
=== Uninstall Script (no changes) === |
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // ab030635 -> 81b0529a ===
--- /tmp/old.NIHOXv 2026-03-17 20:40:37.216027936 +0000
+++ /tmp/new.kU1rib 2026-03-17 20:40:37.216027936 +0000
@@ -1,78 +1,5 @@
-$exeFilePath = "${env:INSTALLER_PATH}"
-
-$exitCode = 0
-
-try {
-
-# Copy the installer to a public folder so that all can access it
-# users
-$exeFilename = Split-Path $exeFilePath -leaf
-Copy-Item -Path $exeFilePath -Destination "${env:PUBLIC}" -Force
-$exeFilePath = "${env:PUBLIC}\$exeFilename"
-
-# Task properties. The task will be started by the logged in user
-$action = New-ScheduledTaskAction -Execute "$exeFilePath" `
- -Argument "--vivaldi-silent --do-not-launch-chrome"
-$trigger = New-ScheduledTaskTrigger -AtLogOn
-$userName = (Get-CimInstance Win32_Process -Filter 'name = "explorer.exe"' | Invoke-CimMethod -MethodName getowner).User
-$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
-
-# Create a task object with the properties defined above
-$task = New-ScheduledTask -Action $action -Trigger $trigger `
- -Settings $settings
-
-# Register the task
-$taskName = "fleet-install-$exeFilename"
-Register-ScheduledTask "$taskName" -InputObject $task -User "$userName"
-
-# keep track of the start time to cancel if taking too long to start
-$startDate = Get-Date
-
-# Start the task now that it is ready
-Start-ScheduledTask -TaskName "$taskName" -TaskPath "\"
-
-# Wait for the task to be running
-$state = (Get-ScheduledTask -TaskName "$taskName").State
-Write-Host "ScheduledTask is '$state'"
-
-while ($state -ne "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting to run .exe..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 1
- $state = (Get-ScheduledTask -TaskName "$taskName").State
-}
-
-# Wait for the task to be done
-$state = (Get-ScheduledTask -TaskName "$taskName").State
-while ($state -eq "Running") {
- Write-Host "ScheduledTask is '$state'. Waiting for .exe to complete..."
-
- $endDate = Get-Date
- $elapsedTime = New-Timespan -Start $startDate -End $endDate
- if ($elapsedTime.TotalSeconds -gt 120) {
- Throw "Timed-out waiting for scheduled task state."
- }
-
- Start-Sleep -Seconds 10
- $state = (Get-ScheduledTask -TaskName "$taskName").State
-}
-
-# Remove task
-Write-Host "Removing ScheduledTask: $taskName."
-Unregister-ScheduledTask -TaskName "$taskName" -Confirm:$false
-
-} catch {
- Write-Host "Error: $_"
- $exitCode = 1
-} finally {
- # Remove installer
- Remove-Item -Path $exeFilePath -Force
-}
-
-Exit $exitCode
+# Install Vivaldi silently (user-scoped Chromium-based browser)
+$process = Start-Process -FilePath $env:INSTALLER_PATH `
+ -ArgumentList "--vivaldi-silent --do-not-launch-chrome" `
+ -NoNewWindow -PassThru -Wait
+Exit $process.ExitCode
=== Uninstall Script (no changes) === |
# Conflicts: # ee/maintained-apps/outputs/apps.json # frontend/pages/SoftwarePage/components/icons/index.ts
- Rebase onto current main and regenerate darwin/windows outputs via the ingester at version 8.0.4033.46 (refs content-addressed, not hand-edited). - Windows: add --system-level to the install script and set installer_scope to machine. Fleet installs as SYSTEM, so without --system-level the Chromium-based installer landed in the SYSTEM profile and was invisible to the real user. The exists query (HKLM) and uninstall script already assumed a machine install. Flag verified against Chrome's FMA script and Vivaldi docs. - Fill catalog descriptions for both platforms.
…ing parsing
- Reorder the Windows uninstall registry search to HKLM-first (machine-wide
--system-level install registers under HKLM), with HKCU as a stale-entry
fallback, and require Publisher 'Vivaldi Technologies AS.' in addition to
DisplayName to avoid matching the wrong entry.
- Replace the brittle UninstallString .Split('"') parsing with the canonical
three-case defensive regex (quoted / unquoted-with-spaces / bare token), so
unquoted registry commands no longer break Start-Process.
- Regenerate vivaldi/windows.json (content-addressed uninstall ref).
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install // d8e37a25 -> 6a60dca7 ===
--- /tmp/old.CGH0fk 2026-06-15 14:26:56.346038174 +0000
+++ /tmp/new.KKs3t2 2026-06-15 14:26:56.347038188 +0000
@@ -1,8 +1,8 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
-TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)")
+TMPDIR=$(dirname "$(realpath "$INSTALLER_PATH")")
# functions
quit_and_track_application() {
@@ -11,14 +11,16 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
eval "export $var_name=0"
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
eval "export $var_name=0"
return
@@ -63,15 +65,28 @@
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'."
return
fi
echo "Relaunching application '$bundle_id'..."
- # Try to launch the application
- if osascript -e "tell application id \"$bundle_id\" to activate" >/dev/null 2>&1; then
+ # Launch the app in the logged-in user's GUI session. Apps launched by root
+ # won't register with the user's Dock/GUI, so run 'open' as the console user.
+ # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace
+ # and GUI session — 'sudo -u' alone doesn't do this, which can cause
+ # LSOpenURLsWithRole() failures even when 'open' exits 0.
+ local open_status=0
+ if [[ $EUID -eq 0 ]]; then
+ local console_uid
+ console_uid=$(id -u "$console_user")
+ /bin/launchctl asuser "$console_uid" sudo -u "$console_user" open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ else
+ open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ fi
+
+ if [[ $open_status -eq 0 ]]; then
echo "Application '$bundle_id' relaunched successfully."
else
echo "Failed to relaunch application '$bundle_id'."
@@ -81,9 +96,9 @@
# extract contents
MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)
-hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH"
+yes | hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" || exit 1
sudo cp -R "$MOUNT_POINT"/* "$TMPDIR"
-hdiutil detach "$MOUNT_POINT"
+hdiutil detach "$MOUNT_POINT" || true
# copy to the applications folder
quit_and_track_application 'com.vivaldi.Vivaldi'
if [ -d "$APPDIR/Vivaldi.app" ]; then
=== Uninstall // 83339974 -> 7c1876ca ===
--- /tmp/old.kHW71h 2026-06-15 14:26:56.368038468 +0000
+++ /tmp/new.i0rzpI 2026-06-15 14:26:56.368038468 +0000
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
@@ -10,13 +10,15 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
return
fi
@@ -55,6 +57,31 @@
fi
local trash="/Users/$logged_in_user/.Trash"
+
+ # If the target contains glob characters, expand it and move each match.
+ if [[ "$target_file" == *[*?[]* ]]; then
+ local file file_name
+ local matched=false
+ local i=0
+ # compgen -G expands the (quoted) pattern itself, so paths containing
+ # spaces glob correctly; reading line by line keeps each match intact.
+ while IFS= read -r file; do
+ [[ -n "$file" ]] || continue
+ [[ -e "$file" || -L "$file" ]] || continue
+ matched=true
+ i=$((i + 1))
+ file_name="$(basename "$file")"
+ echo "removing $file."
+ # The per-match counter keeps matches that share a basename from
+ # overwriting each other in the trash.
+ mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}"
+ done < <(compgen -G "$target_file" 2>/dev/null)
+ if [[ "$matched" == false ]]; then
+ echo "$target_file doesn't exist."
+ fi
+ return
+ fi
+
local file_name="$(basename "${target_file}")"
if [[ -e "$target_file" ]]; thenee/maintained-apps/outputs/vivaldi/windows.json=== Install Script (no changes) ===
=== Uninstall // 741609fc -> 7b14fec4 ===
--- /tmp/old.CdQeJi 2026-06-15 14:26:56.405038963 +0000
+++ /tmp/new.3yiiI0 2026-06-15 14:26:56.405038963 +0000
@@ -3,17 +3,20 @@
# fallback, then runs the Chromium uninstaller with --force-uninstall.
$displayName = "Vivaldi"
+$publisher = "Vivaldi Technologies AS."
+# Install is machine-wide (--system-level), which registers under HKLM, so look
+# there first. HKCU is only a fallback for a stale user-level install.
$paths = @(
- 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
- 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
$uninstall = $null
foreach ($p in $paths) {
$items = Get-ItemProperty "$p\*" -ErrorAction SilentlyContinue | Where-Object {
- $_.DisplayName -and $_.DisplayName -eq $displayName
+ $_.DisplayName -eq $displayName -and $_.Publisher -eq $publisher
}
if ($items) { $uninstall = $items | Select-Object -First 1; break }
}
@@ -33,10 +36,21 @@
$uninstall.UninstallString
}
-# Parse quoted executable from any trailing args in the registry string
-$splitArgs = $uninstallCommand.Split('"')
-$exe = $splitArgs[1]
-$existingArgs = if ($splitArgs.Length -eq 3) { $splitArgs[2].Trim() } else { "" }
+# Parse the executable + trailing args, handling the three registry shapes:
+# quoted, unquoted-with-spaces (capture through .exe), and a bare token.
+if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} else {
+ Write-Host "Unable to parse uninstall command: $uninstallCommand"
+ Exit 1
+}
# Chromium-based uninstaller flags
$uninstallArgs = "$existingArgs --uninstall --force-uninstall".Trim() |
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install // d8e37a25 -> 6a60dca7 ===
--- /tmp/old.g8QpLm 2026-06-15 16:06:38.101272972 +0000
+++ /tmp/new.Qh1UXy 2026-06-15 16:06:38.101272972 +0000
@@ -1,8 +1,8 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
-TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)")
+TMPDIR=$(dirname "$(realpath "$INSTALLER_PATH")")
# functions
quit_and_track_application() {
@@ -11,14 +11,16 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
eval "export $var_name=0"
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
eval "export $var_name=0"
return
@@ -63,15 +65,28 @@
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'."
return
fi
echo "Relaunching application '$bundle_id'..."
- # Try to launch the application
- if osascript -e "tell application id \"$bundle_id\" to activate" >/dev/null 2>&1; then
+ # Launch the app in the logged-in user's GUI session. Apps launched by root
+ # won't register with the user's Dock/GUI, so run 'open' as the console user.
+ # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace
+ # and GUI session — 'sudo -u' alone doesn't do this, which can cause
+ # LSOpenURLsWithRole() failures even when 'open' exits 0.
+ local open_status=0
+ if [[ $EUID -eq 0 ]]; then
+ local console_uid
+ console_uid=$(id -u "$console_user")
+ /bin/launchctl asuser "$console_uid" sudo -u "$console_user" open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ else
+ open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ fi
+
+ if [[ $open_status -eq 0 ]]; then
echo "Application '$bundle_id' relaunched successfully."
else
echo "Failed to relaunch application '$bundle_id'."
@@ -81,9 +96,9 @@
# extract contents
MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)
-hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH"
+yes | hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" || exit 1
sudo cp -R "$MOUNT_POINT"/* "$TMPDIR"
-hdiutil detach "$MOUNT_POINT"
+hdiutil detach "$MOUNT_POINT" || true
# copy to the applications folder
quit_and_track_application 'com.vivaldi.Vivaldi'
if [ -d "$APPDIR/Vivaldi.app" ]; then
=== Uninstall // 83339974 -> 7c1876ca ===
--- /tmp/old.mh1ELD 2026-06-15 16:06:38.128272994 +0000
+++ /tmp/new.ut6gZC 2026-06-15 16:06:38.129272995 +0000
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
@@ -10,13 +10,15 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
return
fi
@@ -55,6 +57,31 @@
fi
local trash="/Users/$logged_in_user/.Trash"
+
+ # If the target contains glob characters, expand it and move each match.
+ if [[ "$target_file" == *[*?[]* ]]; then
+ local file file_name
+ local matched=false
+ local i=0
+ # compgen -G expands the (quoted) pattern itself, so paths containing
+ # spaces glob correctly; reading line by line keeps each match intact.
+ while IFS= read -r file; do
+ [[ -n "$file" ]] || continue
+ [[ -e "$file" || -L "$file" ]] || continue
+ matched=true
+ i=$((i + 1))
+ file_name="$(basename "$file")"
+ echo "removing $file."
+ # The per-match counter keeps matches that share a basename from
+ # overwriting each other in the trash.
+ mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}"
+ done < <(compgen -G "$target_file" 2>/dev/null)
+ if [[ "$matched" == false ]]; then
+ echo "$target_file doesn't exist."
+ fi
+ return
+ fi
+
local file_name="$(basename "${target_file}")"
if [[ -e "$target_file" ]]; thenee/maintained-apps/outputs/vivaldi/windows.json=== Install Script (no changes) ===
=== Uninstall // 741609fc -> 7b14fec4 ===
--- /tmp/old.nj7Cfv 2026-06-15 16:06:38.176273034 +0000
+++ /tmp/new.mKBrwn 2026-06-15 16:06:38.176273034 +0000
@@ -3,17 +3,20 @@
# fallback, then runs the Chromium uninstaller with --force-uninstall.
$displayName = "Vivaldi"
+$publisher = "Vivaldi Technologies AS."
+# Install is machine-wide (--system-level), which registers under HKLM, so look
+# there first. HKCU is only a fallback for a stale user-level install.
$paths = @(
- 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
- 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
$uninstall = $null
foreach ($p in $paths) {
$items = Get-ItemProperty "$p\*" -ErrorAction SilentlyContinue | Where-Object {
- $_.DisplayName -and $_.DisplayName -eq $displayName
+ $_.DisplayName -eq $displayName -and $_.Publisher -eq $publisher
}
if ($items) { $uninstall = $items | Select-Object -First 1; break }
}
@@ -33,10 +36,21 @@
$uninstall.UninstallString
}
-# Parse quoted executable from any trailing args in the registry string
-$splitArgs = $uninstallCommand.Split('"')
-$exe = $splitArgs[1]
-$existingArgs = if ($splitArgs.Length -eq 3) { $splitArgs[2].Trim() } else { "" }
+# Parse the executable + trailing args, handling the three registry shapes:
+# quoted, unquoted-with-spaces (capture through .exe), and a bare token.
+if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} else {
+ Write-Host "Unable to parse uninstall command: $uninstallCommand"
+ Exit 1
+}
# Chromium-based uninstaller flags
$uninstallArgs = "$existingArgs --uninstall --force-uninstall".Trim() |
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install // d8e37a25 -> 6a60dca7 ===
--- /tmp/old.ABWLq3 2026-06-16 13:52:27.946074725 +0000
+++ /tmp/new.8u79TK 2026-06-16 13:52:27.947074718 +0000
@@ -1,8 +1,8 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
-TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)")
+TMPDIR=$(dirname "$(realpath "$INSTALLER_PATH")")
# functions
quit_and_track_application() {
@@ -11,14 +11,16 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
eval "export $var_name=0"
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
eval "export $var_name=0"
return
@@ -63,15 +65,28 @@
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'."
return
fi
echo "Relaunching application '$bundle_id'..."
- # Try to launch the application
- if osascript -e "tell application id \"$bundle_id\" to activate" >/dev/null 2>&1; then
+ # Launch the app in the logged-in user's GUI session. Apps launched by root
+ # won't register with the user's Dock/GUI, so run 'open' as the console user.
+ # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace
+ # and GUI session — 'sudo -u' alone doesn't do this, which can cause
+ # LSOpenURLsWithRole() failures even when 'open' exits 0.
+ local open_status=0
+ if [[ $EUID -eq 0 ]]; then
+ local console_uid
+ console_uid=$(id -u "$console_user")
+ /bin/launchctl asuser "$console_uid" sudo -u "$console_user" open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ else
+ open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ fi
+
+ if [[ $open_status -eq 0 ]]; then
echo "Application '$bundle_id' relaunched successfully."
else
echo "Failed to relaunch application '$bundle_id'."
@@ -81,9 +96,9 @@
# extract contents
MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)
-hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH"
+yes | hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" || exit 1
sudo cp -R "$MOUNT_POINT"/* "$TMPDIR"
-hdiutil detach "$MOUNT_POINT"
+hdiutil detach "$MOUNT_POINT" || true
# copy to the applications folder
quit_and_track_application 'com.vivaldi.Vivaldi'
if [ -d "$APPDIR/Vivaldi.app" ]; then
=== Uninstall // 83339974 -> 7c1876ca ===
--- /tmp/old.G7Tdiq 2026-06-16 13:52:27.967074564 +0000
+++ /tmp/new.2oiIcz 2026-06-16 13:52:27.968074556 +0000
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
@@ -10,13 +10,15 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
return
fi
@@ -55,6 +57,31 @@
fi
local trash="/Users/$logged_in_user/.Trash"
+
+ # If the target contains glob characters, expand it and move each match.
+ if [[ "$target_file" == *[*?[]* ]]; then
+ local file file_name
+ local matched=false
+ local i=0
+ # compgen -G expands the (quoted) pattern itself, so paths containing
+ # spaces glob correctly; reading line by line keeps each match intact.
+ while IFS= read -r file; do
+ [[ -n "$file" ]] || continue
+ [[ -e "$file" || -L "$file" ]] || continue
+ matched=true
+ i=$((i + 1))
+ file_name="$(basename "$file")"
+ echo "removing $file."
+ # The per-match counter keeps matches that share a basename from
+ # overwriting each other in the trash.
+ mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}"
+ done < <(compgen -G "$target_file" 2>/dev/null)
+ if [[ "$matched" == false ]]; then
+ echo "$target_file doesn't exist."
+ fi
+ return
+ fi
+
local file_name="$(basename "${target_file}")"
if [[ -e "$target_file" ]]; thenee/maintained-apps/outputs/vivaldi/windows.json=== Install Script (no changes) ===
=== Uninstall // 741609fc -> 7b14fec4 ===
--- /tmp/old.kpUkWy 2026-06-16 13:52:28.013074210 +0000
+++ /tmp/new.dtbWq7 2026-06-16 13:52:28.014074202 +0000
@@ -3,17 +3,20 @@
# fallback, then runs the Chromium uninstaller with --force-uninstall.
$displayName = "Vivaldi"
+$publisher = "Vivaldi Technologies AS."
+# Install is machine-wide (--system-level), which registers under HKLM, so look
+# there first. HKCU is only a fallback for a stale user-level install.
$paths = @(
- 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
- 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
$uninstall = $null
foreach ($p in $paths) {
$items = Get-ItemProperty "$p\*" -ErrorAction SilentlyContinue | Where-Object {
- $_.DisplayName -and $_.DisplayName -eq $displayName
+ $_.DisplayName -eq $displayName -and $_.Publisher -eq $publisher
}
if ($items) { $uninstall = $items | Select-Object -First 1; break }
}
@@ -33,10 +36,21 @@
$uninstall.UninstallString
}
-# Parse quoted executable from any trailing args in the registry string
-$splitArgs = $uninstallCommand.Split('"')
-$exe = $splitArgs[1]
-$existingArgs = if ($splitArgs.Length -eq 3) { $splitArgs[2].Trim() } else { "" }
+# Parse the executable + trailing args, handling the three registry shapes:
+# quoted, unquoted-with-spaces (capture through .exe), and a bare token.
+if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} else {
+ Write-Host "Unable to parse uninstall command: $uninstallCommand"
+ Exit 1
+}
# Chromium-based uninstaller flags
$uninstallArgs = "$existingArgs --uninstall --force-uninstall".Trim() |
There was a problem hiding this comment.
Pull request overview
Adds Vivaldi to Fleet’s Fleet-maintained apps (FMA) catalog for macOS (Homebrew-based ingestion with an external ref enricher) and Windows (winget-based ingestion with custom PowerShell install/uninstall), plus a frontend icon mapping so Vivaldi displays with the correct logo in the Software UI.
Changes:
- Add Vivaldi Homebrew + winget inputs and generated outputs for
vivaldi/darwinandvivaldi/windows. - Introduce a Homebrew external ref enricher to swap Homebrew’s unsupported
tar.xzURL for Vivaldi’s direct universal DMG. - Add a Vivaldi icon component and wire it into the software-name-to-icon map.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx | Adds the Vivaldi icon component. |
| frontend/pages/SoftwarePage/components/icons/index.ts | Registers the Vivaldi icon in the icon map. |
| ee/maintained-apps/outputs/vivaldi/windows.json | Generated Windows Vivaldi catalog entry + embedded script refs. |
| ee/maintained-apps/outputs/vivaldi/darwin.json | Generated macOS Vivaldi catalog entry + embedded script refs. |
| ee/maintained-apps/outputs/apps.json | Adds Vivaldi entries to the generated app catalog index. |
| ee/maintained-apps/inputs/winget/vivaldi.json | Defines the winget input manifest for Vivaldi Windows. |
| ee/maintained-apps/inputs/winget/scripts/vivaldi_install.ps1 | Custom machine-wide silent install script for Windows. |
| ee/maintained-apps/inputs/winget/scripts/vivaldi_uninstall.ps1 | Custom uninstall script for Windows (registry lookup + forced uninstall). |
| ee/maintained-apps/inputs/homebrew/vivaldi.json | Defines the Homebrew input manifest for Vivaldi macOS. |
| ee/maintained-apps/ingesters/homebrew/external_refs/vivaldi.go | Adds the DMG URL override enricher for Vivaldi macOS. |
| ee/maintained-apps/ingesters/homebrew/external_refs/main.go | Registers the Vivaldi external ref enricher. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Script Diff Resultsee/maintained-apps/outputs/vivaldi/darwin.json=== Install // d8e37a25 -> 6a60dca7 ===
--- /tmp/old.ubOCeZ 2026-06-16 15:17:52.159606724 +0000
+++ /tmp/new.rsrFOZ 2026-06-16 15:17:52.159606724 +0000
@@ -1,8 +1,8 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
-TMPDIR=$(dirname "$(realpath $INSTALLER_PATH)")
+TMPDIR=$(dirname "$(realpath "$INSTALLER_PATH")")
# functions
quit_and_track_application() {
@@ -11,14 +11,16 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
eval "export $var_name=0"
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
eval "export $var_name=0"
return
@@ -63,15 +65,28 @@
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'."
return
fi
echo "Relaunching application '$bundle_id'..."
- # Try to launch the application
- if osascript -e "tell application id \"$bundle_id\" to activate" >/dev/null 2>&1; then
+ # Launch the app in the logged-in user's GUI session. Apps launched by root
+ # won't register with the user's Dock/GUI, so run 'open' as the console user.
+ # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace
+ # and GUI session — 'sudo -u' alone doesn't do this, which can cause
+ # LSOpenURLsWithRole() failures even when 'open' exits 0.
+ local open_status=0
+ if [[ $EUID -eq 0 ]]; then
+ local console_uid
+ console_uid=$(id -u "$console_user")
+ /bin/launchctl asuser "$console_uid" sudo -u "$console_user" open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ else
+ open -b "$bundle_id" >/dev/null 2>&1 || open_status=$?
+ fi
+
+ if [[ $open_status -eq 0 ]]; then
echo "Application '$bundle_id' relaunched successfully."
else
echo "Failed to relaunch application '$bundle_id'."
@@ -81,9 +96,9 @@
# extract contents
MOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)
-hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH"
+yes | hdiutil attach -plist -nobrowse -readonly -mountpoint "$MOUNT_POINT" "$INSTALLER_PATH" || exit 1
sudo cp -R "$MOUNT_POINT"/* "$TMPDIR"
-hdiutil detach "$MOUNT_POINT"
+hdiutil detach "$MOUNT_POINT" || true
# copy to the applications folder
quit_and_track_application 'com.vivaldi.Vivaldi'
if [ -d "$APPDIR/Vivaldi.app" ]; then
=== Uninstall // 83339974 -> 7c1876ca ===
--- /tmp/old.2QSNqm 2026-06-16 15:17:52.177607518 +0000
+++ /tmp/new.IDXQPN 2026-06-16 15:17:52.178607562 +0000
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# variables
APPDIR="/Applications/"
@@ -10,13 +10,15 @@
local timeout_duration=10
# check if the application is running
- if ! osascript -e "application id \"$bundle_id\" is running" 2>/dev/null; then
+ local app_running
+ app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+ if [[ "$app_running" != "true" ]]; then
return
fi
local console_user
console_user=$(stat -f "%Su" /dev/console)
- if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then
+ if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
return
fi
@@ -55,6 +57,31 @@
fi
local trash="/Users/$logged_in_user/.Trash"
+
+ # If the target contains glob characters, expand it and move each match.
+ if [[ "$target_file" == *[*?[]* ]]; then
+ local file file_name
+ local matched=false
+ local i=0
+ # compgen -G expands the (quoted) pattern itself, so paths containing
+ # spaces glob correctly; reading line by line keeps each match intact.
+ while IFS= read -r file; do
+ [[ -n "$file" ]] || continue
+ [[ -e "$file" || -L "$file" ]] || continue
+ matched=true
+ i=$((i + 1))
+ file_name="$(basename "$file")"
+ echo "removing $file."
+ # The per-match counter keeps matches that share a basename from
+ # overwriting each other in the trash.
+ mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}"
+ done < <(compgen -G "$target_file" 2>/dev/null)
+ if [[ "$matched" == false ]]; then
+ echo "$target_file doesn't exist."
+ fi
+ return
+ fi
+
local file_name="$(basename "${target_file}")"
if [[ -e "$target_file" ]]; thenee/maintained-apps/outputs/vivaldi/windows.json=== Install Script (no changes) ===
=== Uninstall // 741609fc -> 7b14fec4 ===
--- /tmp/old.AH68g9 2026-06-16 15:17:52.218609327 +0000
+++ /tmp/new.5QgFGs 2026-06-16 15:17:52.218609327 +0000
@@ -3,17 +3,20 @@
# fallback, then runs the Chromium uninstaller with --force-uninstall.
$displayName = "Vivaldi"
+$publisher = "Vivaldi Technologies AS."
+# Install is machine-wide (--system-level), which registers under HKLM, so look
+# there first. HKCU is only a fallback for a stale user-level install.
$paths = @(
- 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
- 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
$uninstall = $null
foreach ($p in $paths) {
$items = Get-ItemProperty "$p\*" -ErrorAction SilentlyContinue | Where-Object {
- $_.DisplayName -and $_.DisplayName -eq $displayName
+ $_.DisplayName -eq $displayName -and $_.Publisher -eq $publisher
}
if ($items) { $uninstall = $items | Select-Object -First 1; break }
}
@@ -33,10 +36,21 @@
$uninstall.UninstallString
}
-# Parse quoted executable from any trailing args in the registry string
-$splitArgs = $uninstallCommand.Split('"')
-$exe = $splitArgs[1]
-$existingArgs = if ($splitArgs.Length -eq 3) { $splitArgs[2].Trim() } else { "" }
+# Parse the executable + trailing args, handling the three registry shapes:
+# quoted, unquoted-with-spaces (capture through .exe), and a bare token.
+if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
+ $exe = $Matches[1]
+ $existingArgs = $Matches[2].Trim()
+} else {
+ Write-Host "Unable to parse uninstall command: $uninstallCommand"
+ Exit 1
+}
# Chromium-based uninstaller flags
$uninstallArgs = "$existingArgs --uninstall --force-uninstall".Trim() |
Related issue: N/A — adds Vivaldi to the Fleet-maintained apps catalog.
Summary
Adds Vivaldi as a Fleet-maintained app for macOS and Windows.
vivaldi/darwin) — Homebrew cask input plus aVivaldiDMGInstallerenricher that redirects Homebrew's unsupportedtar.xzdownload to Vivaldi's direct universal DMG (https://downloads.vivaldi.com/stable/Vivaldi.{version}.universal.dmg), keepinginstaller_format: dmgso the standardhdiutil-based install/uninstall scripts are generated.unique_identifieris the verified bundle idcom.vivaldi.Vivaldi.vivaldi/windows) — winget input (Vivaldi.Vivaldi) with custom install/uninstall scripts. Installs machine-wide with--vivaldi-silent --do-not-launch-chrome --system-level. Because Fleet runs installers asSYSTEM, the--system-levelflag is required — without it the Chromium-based installer lands in theSYSTEMprofile and is invisible to the real user. The exists query matches the registryDisplayNameVivaldiand verifiedPublisherVivaldi Technologies AS.; the uninstall script looks up the entry under HKLM (with an HKCU fallback) and runs the Chromium uninstaller with--force-uninstall.Outputs were regenerated via the ingester (
go run cmd/maintained-apps/main.go --slug=...) at Vivaldi 8.0.4033.46; script refs are content-addressed, not hand-edited.Changes
ee/maintained-apps/inputs/homebrew/vivaldi.json,ingesters/homebrew/external_refs/vivaldi.go(+ registration inmain.go) — macOS input and DMG-URL enricheree/maintained-apps/inputs/winget/vivaldi.json+scripts/vivaldi_install.ps1/vivaldi_uninstall.ps1— Windows input and custom machine-wide install/uninstallee/maintained-apps/outputs/vivaldi/darwin.json,vivaldi/windows.json,outputs/apps.json— generated catalog entries (with descriptions)frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx+index.ts,website/assets/images/app-icon-vivaldi-60x60@2x.png— UI/website iconChecklist for submitter
Testing
installer_url, SHA, and non-empty install/uninstall script refs.unique_identifier(com.vivaldi.Vivaldi) and Windows identity (DisplayNameVivaldi, PublisherVivaldi Technologies AS.) against the cask/winget manifests.apps.jsonvalidates as JSON;go build ./cmd/maintained-apps/...andGOOS=windows go build ./cmd/maintained-apps/validate/pass.Summary by CodeRabbit