From 21fd5d8a2f66704f56b1e5c9e9cd8238d4d36fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:35:26 +0200 Subject: [PATCH 01/27] Reorder properties to wanted order --- schema.json | 126 ++++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/schema.json b/schema.json index 6c24d4da20..3bd2c564dc 100644 --- a/schema.json +++ b/schema.json @@ -533,6 +533,41 @@ "description": "A comment.", "$ref": "#/definitions/stringOrArrayOfStrings" }, + "version": { + "pattern": "^[\\w\\.\\-+_]+$", + "type": "string" + }, + "description": { + "type": "string" + }, + "homepage": { + "format": "uri", + "type": "string" + }, + "license": { + "$ref": "#/definitions/license" + }, + "notes": { + "$ref": "#/definitions/stringOrArrayOfStrings" + }, + "depends": { + "$ref": "#/definitions/stringOrArrayOfStrings" + }, + "suggest": { + "additionalProperties": false, + "patternProperties": { + "^(.*)$": { + "$ref": "#/definitions/stringOrArrayOfStrings" + } + }, + "type": "object" + }, + "url": { + "$ref": "#/definitions/uriOrArrayOfUris" + }, + "hash": { + "$ref": "#/definitions/hash" + }, "architecture": { "additionalProperties": false, "properties": { @@ -548,72 +583,49 @@ }, "type": "object" }, - "autoupdate": { - "$ref": "#/definitions/autoupdate" + "innosetup": { + "description": "True if the installer InnoSetup based. Found in https://github.com/ScoopInstaller/Main/search?l=JSON&q=innosetup", + "type": "boolean" }, - "bin": { - "$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings" + "extract_dir": { + "$ref": "#/definitions/stringOrArrayOfStrings" }, - "persist": { - "$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings" + "extract_to": { + "$ref": "#/definitions/stringOrArrayOfStrings" }, - "checkver": { - "$ref": "#/definitions/checkver" + "pre_install": { + "$ref": "#/definitions/stringOrArrayOfStrings" }, - "cookie": { - "description": "Undocumented: Found at https://github.com/se35710/scoop-java/search?l=JSON&q=cookie", - "type": "object" + "installer": { + "$ref": "#/definitions/installer" }, - "depends": { + "post_install": { "$ref": "#/definitions/stringOrArrayOfStrings" }, - "description": { - "type": "string" - }, "env_add_path": { "$ref": "#/definitions/stringOrArrayOfStrings" }, "env_set": { "type": "object" }, - "extract_dir": { - "$ref": "#/definitions/stringOrArrayOfStrings" - }, - "extract_to": { - "$ref": "#/definitions/stringOrArrayOfStrings" - }, - "hash": { - "$ref": "#/definitions/hash" - }, - "homepage": { - "format": "uri", - "type": "string" - }, - "innosetup": { - "description": "True if the installer InnoSetup based. Found in https://github.com/ScoopInstaller/Main/search?l=JSON&q=innosetup", - "type": "boolean" + "bin": { + "$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings" }, - "installer": { - "$ref": "#/definitions/installer" + "shortcuts": { + "$ref": "#/definitions/shortcutsArray" }, - "license": { - "$ref": "#/definitions/license" + "persist": { + "$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings" }, - "notes": { + "pre_uninstall": { "$ref": "#/definitions/stringOrArrayOfStrings" }, - "post_install": { - "$ref": "#/definitions/stringOrArrayOfStrings" + "uninstaller": { + "$ref": "#/definitions/uninstaller" }, "post_uninstall": { "$ref": "#/definitions/stringOrArrayOfStrings" }, - "pre_install": { - "$ref": "#/definitions/stringOrArrayOfStrings" - }, - "pre_uninstall": { - "$ref": "#/definitions/stringOrArrayOfStrings" - }, "psmodule": { "additionalProperties": false, "properties": { @@ -623,27 +635,15 @@ }, "type": "object" }, - "shortcuts": { - "$ref": "#/definitions/shortcutsArray" - }, - "suggest": { - "additionalProperties": false, - "patternProperties": { - "^(.*)$": { - "$ref": "#/definitions/stringOrArrayOfStrings" - } - }, - "type": "object" - }, - "uninstaller": { - "$ref": "#/definitions/uninstaller" + "checkver": { + "$ref": "#/definitions/checkver" }, - "url": { - "$ref": "#/definitions/uriOrArrayOfUris" + "autoupdate": { + "$ref": "#/definitions/autoupdate" }, - "version": { - "pattern": "^[\\w\\.\\-+_]+$", - "type": "string" + "cookie": { + "description": "Undocumented: Found at https://github.com/se35710/scoop-java/search?l=JSON&q=cookie", + "type": "object" } }, "if": { From 16ea6556f9f47676a4cea9fd78a0423671f3fa31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:45:01 +0200 Subject: [PATCH 02/27] Add sort function to json.ps1 --- lib/json.ps1 | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/json.ps1 b/lib/json.ps1 index d6fd3621b5..242826ee8a 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -212,3 +212,44 @@ function normalize_values([psobject] $json) { return $json } + +function Sort-ScoopManifestRootProperties { + [OutputType([string])] + param( + [Parameter(Mandatory)] + [ValidateScript({[System.IO.File]::Exists($_)})] + [string] $File + ) + + # Get wanted order from Scoop manifest schema + $WantedOrder = [string[]]( + ( + ConvertFrom-Json -InputObject ( + '{0}\..\schema.json' -f $PSScriptRoot + ) + ).'properties'.'PSObject'.'Properties'.'Name' + ) + + # Store current JSON as an object for further processing + $Current = [PSCustomObject]( + ConvertFrom-Json -InputObject ( + Get-Content -Raw -Path $File + ) + ) + + # Create empty new object where properties will be added to + $Sorted = [PSCustomObject]::new() + + # Add properties from $Current to $Sorted ordered by $WantedOrder + $Current.'PSObject'.'Properties'.'Name' | + Sort-Object -Property @{ + 'Expression' = { + [byte]($WantedOrder.IndexOf($_)) + } + } | ForEach-Object -Process { + $null = Add-Member -InputObject $Sorted -NotePropertyName $_ -NotePropertyValue $Current.$_ + } + + # Return the sorted object + ConvertTo-Json -Depth 8 -InputObject $Sorted +} From 98ce740e9aaa27f52b65fa620fa16e074b92285f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:56:01 +0200 Subject: [PATCH 03/27] Take PSCustomObject instead, let parse_json do the parsing --- lib/json.ps1 | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index 242826ee8a..11fe3859a1 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -214,11 +214,11 @@ function normalize_values([psobject] $json) { } function Sort-ScoopManifestRootProperties { - [OutputType([string])] - param( + [OutputType([PSCustomObject])] + + Param( [Parameter(Mandatory)] - [ValidateScript({[System.IO.File]::Exists($_)})] - [string] $File + [PSCustomObject] $JsonAsObject ) # Get wanted order from Scoop manifest schema @@ -230,26 +230,19 @@ function Sort-ScoopManifestRootProperties { ).'properties'.'PSObject'.'Properties'.'Name' ) - # Store current JSON as an object for further processing - $Current = [PSCustomObject]( - ConvertFrom-Json -InputObject ( - Get-Content -Raw -Path $File - ) - ) - # Create empty new object where properties will be added to $Sorted = [PSCustomObject]::new() # Add properties from $Current to $Sorted ordered by $WantedOrder - $Current.'PSObject'.'Properties'.'Name' | + $JsonAsObject.'PSObject'.'Properties'.'Name' | Sort-Object -Property @{ 'Expression' = { [byte]($WantedOrder.IndexOf($_)) } } | ForEach-Object -Process { - $null = Add-Member -InputObject $Sorted -NotePropertyName $_ -NotePropertyValue $Current.$_ + $null = Add-Member -InputObject $Sorted -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ } # Return the sorted object - ConvertTo-Json -Depth 8 -InputObject $Sorted + $Sorted } From 5983d28e55c179f17841dc8beb7019b1a7ef3972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:56:11 +0200 Subject: [PATCH 04/27] Added sorting manifest as a step --- bin/formatjson.ps1 | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/bin/formatjson.ps1 b/bin/formatjson.ps1 index e66c6f69a3..476019bf8a 100644 --- a/bin/formatjson.ps1 +++ b/bin/formatjson.ps1 @@ -34,11 +34,21 @@ param( $Dir = Convert-Path $Dir Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object { - $file = $_.FullName - # beautify - $json = parse_json $file | ConvertToPrettyJson + # Path of file + $file = [string] $_.'FullName' - # convert to 4 spaces - $json = $json -replace "`t", ' ' + # Parse JSON + $json = [PSCustomObject](parse_json -path $file) + + # Sort JSON root properties + $json = [PSCustomObject](Sort-ScoopManifestRootProperties -JsonAsObject $json) + + # Beautify + $json = [string](ConvertToPrettyJson -data $json) + + # Convert to 4 spaces + $json = [string]($json -replace "`t", ' ') + + # Overwrite file content [System.IO.File]::WriteAllLines($file, $json) } From cd06740cb40c594e642e9f30d333ead48e0cd447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:56:56 +0200 Subject: [PATCH 05/27] Make variable name more telling --- lib/json.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index 11fe3859a1..ad74cab6bb 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -231,7 +231,7 @@ function Sort-ScoopManifestRootProperties { ) # Create empty new object where properties will be added to - $Sorted = [PSCustomObject]::new() + $SortedObject = [PSCustomObject]::new() # Add properties from $Current to $Sorted ordered by $WantedOrder $JsonAsObject.'PSObject'.'Properties'.'Name' | @@ -240,9 +240,9 @@ function Sort-ScoopManifestRootProperties { [byte]($WantedOrder.IndexOf($_)) } } | ForEach-Object -Process { - $null = Add-Member -InputObject $Sorted -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ + $null = Add-Member -InputObject $SortedObject -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ } # Return the sorted object - $Sorted + $SortedObject } From a579f69d1f69bcce78c6047001d1b3e6592f7b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:02:58 +0200 Subject: [PATCH 06/27] Added changes to changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3795ce5dc3..9d1747fd26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## [Unreleased](https://github.com/ScoopInstaller/Scoop/compare/v0.5.3...develop) +### Features + +- **schema**: Reorder root properties to match the wanted order. +- **json**: Add function `Sort-ScoopManifestRootProperties` which orders a manifest's root properties to match `schema.json`. +- **formatjson**: Use `Sort-ScoopManifestRootProperties` to sort manifests root properties. + ### Bug Fixes - **scoop-download:** Fix function `nightly_version` not defined error ([#6386](https://github.com/ScoopInstaller/Scoop/issues/6386)) From c0b41a273b31085bb91fab593773fd2c98e26ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:06:38 +0200 Subject: [PATCH 07/27] Don't use aliases --- bin/formatjson.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/formatjson.ps1 b/bin/formatjson.ps1 index 476019bf8a..e8c1fd2bbd 100644 --- a/bin/formatjson.ps1 +++ b/bin/formatjson.ps1 @@ -18,7 +18,7 @@ param( [String] $App = '*', [Parameter(Mandatory = $true)] [ValidateScript( { - if (!(Test-Path $_ -Type Container)) { + if (-not (Test-Path $_ -Type Container)) { throw "$_ is not a directory!" } else { $true From 25a0b595df624989f1af34f62208a592536ac6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:22:56 +0200 Subject: [PATCH 08/27] Use existing function parse_json instead --- lib/json.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index ad74cab6bb..6431bb7e0f 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -222,9 +222,10 @@ function Sort-ScoopManifestRootProperties { ) # Get wanted order from Scoop manifest schema + Write-Debug -Message ('{0}\..\schema.json' -f $PSScriptRoot) $WantedOrder = [string[]]( ( - ConvertFrom-Json -InputObject ( + parse_json -path ( '{0}\..\schema.json' -f $PSScriptRoot ) ).'properties'.'PSObject'.'Properties'.'Name' From 2c21e145ad7aa94fc26cd4f9c5ae52485a444f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:23:26 +0200 Subject: [PATCH 09/27] Make parse_json function more readable --- lib/manifest.ps1 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/manifest.ps1 b/lib/manifest.ps1 index 29c898b6a7..5762d957d5 100644 --- a/lib/manifest.ps1 +++ b/lib/manifest.ps1 @@ -2,10 +2,16 @@ function manifest_path($app, $bucket) { (Get-ChildItem (Find-BucketDirectory $bucket) -Filter "$(sanitary_path $app).json" -Recurse).FullName } -function parse_json($path) { - if ($null -eq $path -or !(Test-Path $path)) { return $null } +function parse_json { + Param( + [Parameter(Mandatory)] + [string] $path + ) + if ([string]::IsNullOrWhiteSpace($path) -or -not [System.IO.Path]::Exists($path)) { + return $null + } try { - Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop + Get-Content -Path $path -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop } catch { warn "Error parsing JSON at '$path'." } From d9f07a3b518deccfb79fc01694f532c6cc27cc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:29:00 +0200 Subject: [PATCH 10/27] Fix goof by me --- lib/manifest.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/manifest.ps1 b/lib/manifest.ps1 index 5762d957d5..5b48306be5 100644 --- a/lib/manifest.ps1 +++ b/lib/manifest.ps1 @@ -7,7 +7,7 @@ function parse_json { [Parameter(Mandatory)] [string] $path ) - if ([string]::IsNullOrWhiteSpace($path) -or -not [System.IO.Path]::Exists($path)) { + if ([string]::IsNullOrWhiteSpace($path) -or -not [System.IO.File]::Exists($path)) { return $null } try { From 37aa4596d5f1e5b8d914786b6f038e597ac52960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:33:57 +0200 Subject: [PATCH 11/27] Read and parse schema.json once --- lib/json.ps1 | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index 6431bb7e0f..ffc1e2b978 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -222,14 +222,16 @@ function Sort-ScoopManifestRootProperties { ) # Get wanted order from Scoop manifest schema - Write-Debug -Message ('{0}\..\schema.json' -f $PSScriptRoot) - $WantedOrder = [string[]]( - ( - parse_json -path ( - '{0}\..\schema.json' -f $PSScriptRoot - ) - ).'properties'.'PSObject'.'Properties'.'Name' - ) + if ([string]::IsNullOrWhiteSpace($Script:WantedOrder)) { + Write-Debug -Message ('{0}\..\schema.json' -f $PSScriptRoot) + $Script:WantedOrder = [string[]]( + ( + parse_json -path ( + '{0}\..\schema.json' -f $PSScriptRoot + ) + ).'properties'.'PSObject'.'Properties'.'Name' + ) + } # Create empty new object where properties will be added to $SortedObject = [PSCustomObject]::new() From e4408024e3493ccfec72a509de259c6e63a2df19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:37:23 +0200 Subject: [PATCH 12/27] Add some failproof to make sure only expected keys are present --- lib/json.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/json.ps1 b/lib/json.ps1 index ffc1e2b978..e2ebae9d47 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -233,6 +233,14 @@ function Sort-ScoopManifestRootProperties { ) } + # Failproof - Make sure input does not have keys not defined in schema.json + $KeysNotInSchema = [string[]]( + $JsonAsObject.'PSObject'.'Properties'.'Name'.Where{$_ -cnotin $WantedOrder} + ) + if ($KeysNotInSchema.'Count' -gt 0) { + Throw ('Manifest contains keys not defined in schema.json: "{0}".' -f ($KeysNotInSchema -join ", ")) + } + # Create empty new object where properties will be added to $SortedObject = [PSCustomObject]::new() From 802b26f37498bb732de704b097295ff8c3bd3ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:37:34 +0200 Subject: [PATCH 13/27] Remove debug --- lib/json.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index e2ebae9d47..7212d25536 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -223,7 +223,6 @@ function Sort-ScoopManifestRootProperties { # Get wanted order from Scoop manifest schema if ([string]::IsNullOrWhiteSpace($Script:WantedOrder)) { - Write-Debug -Message ('{0}\..\schema.json' -f $PSScriptRoot) $Script:WantedOrder = [string[]]( ( parse_json -path ( From 37d4073d98dc3fb6c323d360902b1f785c13f95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:51:33 +0200 Subject: [PATCH 14/27] Sort childs alphabetically --- lib/json.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/json.ps1 b/lib/json.ps1 index 7212d25536..f2258b2a32 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -253,6 +253,20 @@ function Sort-ScoopManifestRootProperties { $null = Add-Member -InputObject $SortedObject -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ } + # Order childs alphabetically if parent key is of type PSCustomObject + foreach ( + $Key in $SortedObject.'PSObject'.'Properties'.Where{ + $_.'TypeNameOfValue' -eq 'System.Management.Automation.PSCustomObject' + }.'Name' + ) { + $ChildKeys = [string[]]($SortedObject.$Key.'PSObject'.'Properties'.'Name' | Sort-Object) + $ChildObject = [PSCustomObject]::new() + $ChildKeys.ForEach{ + $null = Add-Member -InputObject $ChildObject -NotePropertyName $_ -NotePropertyValue $SortedObject.$Key.$_ + } + $SortedObject.$Key = $ChildObject + } + # Return the sorted object $SortedObject } From 13bd45fd1b3531a3094e77bf97dc2f922e547926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:58:28 +0200 Subject: [PATCH 15/27] Only overwrite JSON file if new content is not empty --- bin/formatjson.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/formatjson.ps1 b/bin/formatjson.ps1 index e8c1fd2bbd..ba6b213c4b 100644 --- a/bin/formatjson.ps1 +++ b/bin/formatjson.ps1 @@ -50,5 +50,7 @@ Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object { $json = [string]($json -replace "`t", ' ') # Overwrite file content - [System.IO.File]::WriteAllLines($file, $json) + if (-not [string]::IsNullOrWhiteSpace($json)) { + [System.IO.File]::WriteAllLines($file, $json) + } } From 8f696f896d54b998ec245a0fda6dfee8abadfdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:04:42 +0200 Subject: [PATCH 16/27] Rename function and add synopsis --- bin/formatjson.ps1 | 4 ++-- lib/json.ps1 | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/formatjson.ps1 b/bin/formatjson.ps1 index ba6b213c4b..fc76d0a15c 100644 --- a/bin/formatjson.ps1 +++ b/bin/formatjson.ps1 @@ -40,8 +40,8 @@ Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object { # Parse JSON $json = [PSCustomObject](parse_json -path $file) - # Sort JSON root properties - $json = [PSCustomObject](Sort-ScoopManifestRootProperties -JsonAsObject $json) + # Sort JSON root properties according to schema.json, and level one child properties alphabetically + $json = [PSCustomObject](Sort-ScoopManifestProperties -JsonAsObject $json) # Beautify $json = [string](ConvertToPrettyJson -data $json) diff --git a/lib/json.ps1 b/lib/json.ps1 index f2258b2a32..3a879ca34e 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -213,7 +213,11 @@ function normalize_values([psobject] $json) { return $json } -function Sort-ScoopManifestRootProperties { +function Sort-ScoopManifestProperties { + <# + .SYNOPSIS + Sort JSON root properties according to schema.json, and level one child properties alphabetically. + #> [OutputType([PSCustomObject])] Param( From d25b11e315e482af02aebc236dd9e53bf2719dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:11:25 +0200 Subject: [PATCH 17/27] Use Scoop function abort instead of PowerShell native Throw --- lib/json.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index 3a879ca34e..1a49e5a275 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -241,7 +241,7 @@ function Sort-ScoopManifestProperties { $JsonAsObject.'PSObject'.'Properties'.'Name'.Where{$_ -cnotin $WantedOrder} ) if ($KeysNotInSchema.'Count' -gt 0) { - Throw ('Manifest contains keys not defined in schema.json: "{0}".' -f ($KeysNotInSchema -join ", ")) + abort ('Manifest contains keys not defined in schema.json: "{0}".' -f ($KeysNotInSchema -join ", ")) } # Create empty new object where properties will be added to From f3dd97e8898108cefee5818494c1d3ca2faf7eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:54:35 +0200 Subject: [PATCH 18/27] Updated changelog to correct name of new function --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1747fd26..c9b831bc81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ ### Features - **schema**: Reorder root properties to match the wanted order. -- **json**: Add function `Sort-ScoopManifestRootProperties` which orders a manifest's root properties to match `schema.json`. -- **formatjson**: Use `Sort-ScoopManifestRootProperties` to sort manifests root properties. +- **json**: Add function `Sort-ScoopManifestProperties` which orders a manifest's root properties to match `schema.json`. +- **formatjson**: Use `Sort-ScoopManifestProperties` to sort manifests root properties. ### Bug Fixes From 1cdae35f2e516f9081c25a74adf0f20f6b7472a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:57:53 +0200 Subject: [PATCH 19/27] Use System.IO.File WriteAllText, because ConvertToPrettyJson returns string, not string array. --- bin/formatjson.ps1 | 2 +- lib/json.ps1 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/formatjson.ps1 b/bin/formatjson.ps1 index fc76d0a15c..b744633286 100644 --- a/bin/formatjson.ps1 +++ b/bin/formatjson.ps1 @@ -51,6 +51,6 @@ Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object { # Overwrite file content if (-not [string]::IsNullOrWhiteSpace($json)) { - [System.IO.File]::WriteAllLines($file, $json) + [System.IO.File]::WriteAllText($file, $json) } } diff --git a/lib/json.ps1 b/lib/json.ps1 index 1a49e5a275..91e53148f5 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -4,6 +4,7 @@ # Still needed in normal powershell function ConvertToPrettyJson { + [OutputType([string])] [CmdletBinding()] Param ( From 18a5f00beacec5e3e19082db11cc3e479f4d0211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:00:26 +0200 Subject: [PATCH 20/27] WriteAllText does not write the final newline, so reverting back to WriteAllLines --- bin/formatjson.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/formatjson.ps1 b/bin/formatjson.ps1 index b744633286..fc76d0a15c 100644 --- a/bin/formatjson.ps1 +++ b/bin/formatjson.ps1 @@ -51,6 +51,6 @@ Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object { # Overwrite file content if (-not [string]::IsNullOrWhiteSpace($json)) { - [System.IO.File]::WriteAllText($file, $json) + [System.IO.File]::WriteAllLines($file, $json) } } From c02d6d81e4e6804d881a0682c9d49869a4db3fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:04:49 +0200 Subject: [PATCH 21/27] Use uin16 instead of byte, just in case Scoop manifest ever gets more than 255 properties --- lib/json.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json.ps1 b/lib/json.ps1 index 91e53148f5..dc1316175d 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -252,7 +252,7 @@ function Sort-ScoopManifestProperties { $JsonAsObject.'PSObject'.'Properties'.'Name' | Sort-Object -Property @{ 'Expression' = { - [byte]($WantedOrder.IndexOf($_)) + [uint16]($WantedOrder.IndexOf($_)) } } | ForEach-Object -Process { $null = Add-Member -InputObject $SortedObject -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ From 80ab7ac49303665a6f36400172c8baf9ea2dcc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:08:52 +0200 Subject: [PATCH 22/27] Validate that input is not null or empty --- lib/json.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/json.ps1 b/lib/json.ps1 index dc1316175d..ea30e3dbf9 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -223,6 +223,7 @@ function Sort-ScoopManifestProperties { Param( [Parameter(Mandatory)] + [ValidateScript({$null -ne $_ -and $_ -ne [PSCustomObject]::new()})] [PSCustomObject] $JsonAsObject ) From 9f429a11ed0d2df2226fb311dbd408b65d5b67ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:01:21 +0200 Subject: [PATCH 23/27] Moved function to manifest.ps1 + made sorting of child keys recursive --- CHANGELOG.md | 3 +- lib/json.ps1 | 63 ----------------------------- lib/manifest.ps1 | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b831bc81..754d038388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ### Features - **schema**: Reorder root properties to match the wanted order. -- **json**: Add function `Sort-ScoopManifestProperties` which orders a manifest's root properties to match `schema.json`. +- **manifest**: Add function `Sort-ScoopManifestProperties` which orders a manifest's root properties to match `schema.json`. +- **manifest**: Add function `Sort-PSCustomObjectKeysRecursively` which orders `[PSCustomObject]` keys recursively. - **formatjson**: Use `Sort-ScoopManifestProperties` to sort manifests root properties. ### Bug Fixes diff --git a/lib/json.ps1 b/lib/json.ps1 index ea30e3dbf9..bbc243df01 100644 --- a/lib/json.ps1 +++ b/lib/json.ps1 @@ -213,66 +213,3 @@ function normalize_values([psobject] $json) { return $json } - -function Sort-ScoopManifestProperties { - <# - .SYNOPSIS - Sort JSON root properties according to schema.json, and level one child properties alphabetically. - #> - [OutputType([PSCustomObject])] - - Param( - [Parameter(Mandatory)] - [ValidateScript({$null -ne $_ -and $_ -ne [PSCustomObject]::new()})] - [PSCustomObject] $JsonAsObject - ) - - # Get wanted order from Scoop manifest schema - if ([string]::IsNullOrWhiteSpace($Script:WantedOrder)) { - $Script:WantedOrder = [string[]]( - ( - parse_json -path ( - '{0}\..\schema.json' -f $PSScriptRoot - ) - ).'properties'.'PSObject'.'Properties'.'Name' - ) - } - - # Failproof - Make sure input does not have keys not defined in schema.json - $KeysNotInSchema = [string[]]( - $JsonAsObject.'PSObject'.'Properties'.'Name'.Where{$_ -cnotin $WantedOrder} - ) - if ($KeysNotInSchema.'Count' -gt 0) { - abort ('Manifest contains keys not defined in schema.json: "{0}".' -f ($KeysNotInSchema -join ", ")) - } - - # Create empty new object where properties will be added to - $SortedObject = [PSCustomObject]::new() - - # Add properties from $Current to $Sorted ordered by $WantedOrder - $JsonAsObject.'PSObject'.'Properties'.'Name' | - Sort-Object -Property @{ - 'Expression' = { - [uint16]($WantedOrder.IndexOf($_)) - } - } | ForEach-Object -Process { - $null = Add-Member -InputObject $SortedObject -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ - } - - # Order childs alphabetically if parent key is of type PSCustomObject - foreach ( - $Key in $SortedObject.'PSObject'.'Properties'.Where{ - $_.'TypeNameOfValue' -eq 'System.Management.Automation.PSCustomObject' - }.'Name' - ) { - $ChildKeys = [string[]]($SortedObject.$Key.'PSObject'.'Properties'.'Name' | Sort-Object) - $ChildObject = [PSCustomObject]::new() - $ChildKeys.ForEach{ - $null = Add-Member -InputObject $ChildObject -NotePropertyName $_ -NotePropertyValue $SortedObject.$Key.$_ - } - $SortedObject.$Key = $ChildObject - } - - # Return the sorted object - $SortedObject -} diff --git a/lib/manifest.ps1 b/lib/manifest.ps1 index 5b48306be5..c0a1ed4073 100644 --- a/lib/manifest.ps1 +++ b/lib/manifest.ps1 @@ -218,3 +218,106 @@ 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 } + +function Sort-PSCustomObjectKeysRecursively { + <# + .SYNOPSIS + Sort PSCustomObject keys recurseively. + #> + [CmdletBinding()] + [OutputType([PSCustomObject])] + param ( + [Parameter(Mandatory, ValueFromPipeline)] + [ValidateScript({$_ -is [System.Management.Automation.PSCustomObject]})] + [object] $InputObject + ) + + Process { + function Convert-Node { + <# + .SYNOPSIS + Helper function that will be called recursively. + #> + param ( + [Parameter(Mandatory)] + [object] $Node + ) + # Recurse if node value is of type [PSCustomObject] + if ($Node -is [System.Management.Automation.PSCustomObject]) { + $OrderedPSCustomObject = [PSCustomObject]::new() + $SortedProperties = $Node.PSObject.Properties | Sort-Object -Property Name + foreach ($Property in $SortedProperties) { + $null = Add-Member -InputObject $OrderedPSCustomObject -NotePropertyName $Property.Name -NotePropertyValue ( + Convert-Node -Node $Property.Value + ) + } + return $OrderedPSCustomObject + } + # Else return the value as is + else { + return $Node + } + } + + # Start the recursive sorting process on the initial input object + return Convert-Node -Node $InputObject + } +} + +function Sort-ScoopManifestProperties { + <# + .SYNOPSIS + Sort JSON root properties according to schema.json, and level one child properties alphabetically. + #> + [OutputType([PSCustomObject])] + + Param( + [Parameter(Mandatory)] + [ValidateScript({$null -ne $_ -and $_ -ne [PSCustomObject]::new()})] + [PSCustomObject] $JsonAsObject + ) + + # Get wanted order from Scoop manifest schema + if ([string]::IsNullOrWhiteSpace($Script:WantedOrder)) { + $Script:WantedOrder = [string[]]( + ( + parse_json -path ( + '{0}\..\schema.json' -f $PSScriptRoot + ) + ).'properties'.'PSObject'.'Properties'.'Name' + ) + } + + # Failproof - Make sure input does not have keys not defined in schema.json + $KeysNotInSchema = [string[]]( + $JsonAsObject.'PSObject'.'Properties'.'Name'.Where{$_ -cnotin $WantedOrder} + ) + if ($KeysNotInSchema.'Count' -gt 0) { + abort ('Manifest contains keys not defined in schema.json: "{0}".' -f ($KeysNotInSchema -join ", ")) + } + + # Create empty new object where properties will be added to + $SortedObject = [PSCustomObject]::new() + + # Add properties from $Current to $Sorted ordered by $WantedOrder + $JsonAsObject.'PSObject'.'Properties'.'Name' | + Sort-Object -Property @{ + 'Expression' = { + [uint16]($WantedOrder.IndexOf($_)) + } + } | ForEach-Object -Process { + $null = Add-Member -InputObject $SortedObject -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ + } + + # Order childs alphabetically recursively, if parent key is of type PSCustomObject + foreach ( + $Key in $SortedObject.'PSObject'.'Properties'.Where{ + $_.'TypeNameOfValue' -eq 'System.Management.Automation.PSCustomObject' + }.'Name' + ) { + $SortedObject.$Key = [PSCustomObject](Sort-PSCustomObjectKeysRecursively -InputObject $SortedObject.$Key) + } + + # Return the sorted object + $SortedObject +} From 203e5a43b8349517b5692ca7e9cc7cdd8de5292d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:49:07 +0200 Subject: [PATCH 24/27] Apply nitpicks from CodeRabbit, except the case of handling arrays as we don't want that. --- lib/manifest.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/manifest.ps1 b/lib/manifest.ps1 index c0a1ed4073..579edb1d8e 100644 --- a/lib/manifest.ps1 +++ b/lib/manifest.ps1 @@ -222,7 +222,7 @@ function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $ar function Sort-PSCustomObjectKeysRecursively { <# .SYNOPSIS - Sort PSCustomObject keys recurseively. + Sort PSCustomObject keys recursively. #> [CmdletBinding()] [OutputType([PSCustomObject])] @@ -233,7 +233,7 @@ function Sort-PSCustomObjectKeysRecursively { ) Process { - function Convert-Node { + function local:Convert-Node { <# .SYNOPSIS Helper function that will be called recursively. From 981c302487dbdca09288b8dbaa47abb536f192c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:50:30 +0200 Subject: [PATCH 25/27] Apply one more nitpick --- lib/manifest.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/manifest.ps1 b/lib/manifest.ps1 index 579edb1d8e..6eb4ab40fd 100644 --- a/lib/manifest.ps1 +++ b/lib/manifest.ps1 @@ -290,7 +290,7 @@ function Sort-ScoopManifestProperties { # Failproof - Make sure input does not have keys not defined in schema.json $KeysNotInSchema = [string[]]( - $JsonAsObject.'PSObject'.'Properties'.'Name'.Where{$_ -cnotin $WantedOrder} + $JsonAsObject.'PSObject'.'Properties'.'Name'.Where{$_ -cnotin $Script:WantedOrder} ) if ($KeysNotInSchema.'Count' -gt 0) { abort ('Manifest contains keys not defined in schema.json: "{0}".' -f ($KeysNotInSchema -join ", ")) @@ -303,7 +303,7 @@ function Sort-ScoopManifestProperties { $JsonAsObject.'PSObject'.'Properties'.'Name' | Sort-Object -Property @{ 'Expression' = { - [uint16]($WantedOrder.IndexOf($_)) + [uint16]($Script:WantedOrder.IndexOf($_)) } } | ForEach-Object -Process { $null = Add-Member -InputObject $SortedObject -NotePropertyName $_ -NotePropertyValue $JsonAsObject.$_ From 931183897b0a07969dafbb150a94d5b7167167ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:16:10 +0200 Subject: [PATCH 26/27] Add test for parse_json and an empty string --- test/Scoop-Manifest.Tests.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Scoop-Manifest.Tests.ps1 b/test/Scoop-Manifest.Tests.ps1 index 232a946bbd..d878e5ebb4 100644 --- a/test/Scoop-Manifest.Tests.ps1 +++ b/test/Scoop-Manifest.Tests.ps1 @@ -11,6 +11,9 @@ Describe 'JSON parse and beautify' -Tag 'Scoop' { It 'fails with invalid json' { { parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Throw } + It 'should not throw if provided an empty string' { + { parse_json '' } | Should -Not -Throw + } } Context 'Beautify JSON' { BeforeDiscovery { From 3fef8035d92b29ff9426e75b6fd48f475b62acb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20R=C3=B8nnestad=20Birkeland?= <6450056+o-l-a-v@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:20:07 +0200 Subject: [PATCH 27/27] Make path not required --- lib/manifest.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/manifest.ps1 b/lib/manifest.ps1 index 6967771e53..06120908f8 100644 --- a/lib/manifest.ps1 +++ b/lib/manifest.ps1 @@ -4,7 +4,7 @@ function manifest_path($app, $bucket) { function parse_json { Param( - [Parameter(Mandatory)] + [Parameter()] [string] $path ) if ([string]::IsNullOrWhiteSpace($path) -or -not [System.IO.File]::Exists($path)) {