diff --git a/.github/workflows/build-index.yml b/.github/workflows/build-index.yml index 0067e61..abd1ca0 100644 --- a/.github/workflows/build-index.yml +++ b/.github/workflows/build-index.yml @@ -33,8 +33,13 @@ jobs: build-index: runs-on: ubuntu-latest steps: + # Full history (not the default shallow depth-1 clone) - Build-Index.ps1 runs `git log` + # per script to record each file's last-modified commit date, which needs the actual + # history to be present, not just the tip commit. - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 # Manual runs are restricted to codeowners (.github/CODEOWNERS) - push-to-main and PR # validation builds are untouched by this and need no such check (push already requires diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6ecce5..1fbc907 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,8 +30,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Generate a new GUID and copy it to the clipboard. #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ†”')] [ScriptTimeout(5000)] [ScriptOutput('None')] @@ -50,8 +50,9 @@ Set-ClipboardText $guid | Attribute | Purpose | |---|---| -| `[ScriptHost('pwsh'\|'powershell')]` | Which PowerShell to run under | +| `[ScriptHost('pwsh'\|'powershell')]` | Which PowerShell to run under. Optional โ€” omit it to run under the user's configured default host instead of pinning one. | | `[ScriptGroup('Name')]` | Category/group in the palette | +| `[ScriptVersion('1.0.0')]` | Script version (SemVer recommended) โ€” lets tools detect when a newer copy is available | | `[ScriptIcon('๐Ÿ†”')]` | Emoji or glyph icon | | `[ScriptTimeout(5000)]` | Timeout in milliseconds | | `[ScriptOutput('None'\|'Toast'\|'Clipboard'\|'Markdown'\|'File'\|'List')]` | How stdout is handled | diff --git a/index.json b/index.json index 8e30684..2a83f82 100644 --- a/index.json +++ b/index.json @@ -9,7 +9,9 @@ "icon": "๐Ÿ“", "author": "phireForge", "tags": [], - "sha": "1545d557d133e55687af505e4c736c560090c143" + "sha": "00210897cbfc3dd6e14bfbd7052274f623f135e5", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/CheatSheet-RegularExpressions.ps1", @@ -19,7 +21,9 @@ "icon": "๐Ÿ”ค", "author": "phireForge", "tags": [], - "sha": "6aa0f5dc405f80ec545a7216d162ce3cfbc0b44e" + "sha": "8fdc369141ee2f1390380415125457c746ee2b11", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Clipboard-LinesToCsv.ps1", @@ -29,7 +33,9 @@ "icon": "๐Ÿ”—", "author": "phireForge", "tags": [], - "sha": "93b296fea562ad6d17ba51490ad668a9a6aff52b" + "sha": "2ad5803ded6629a56e7c9f26b2ffe3d3afed81fe", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Clipboard-LinesToDoubleQuotedCsv.ps1", @@ -39,7 +45,9 @@ "icon": "๐Ÿ”—", "author": "phireForge", "tags": [], - "sha": "251eb0926170fa04e39f941ab39b6320da2ac417" + "sha": "0407711c6bd5cb4742d7267656619f3e12ca65ac", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Clipboard-LinesToSingleQuoteCsv.ps1", @@ -49,7 +57,9 @@ "icon": "๐Ÿ”—", "author": "phireForge", "tags": [], - "sha": "4aa55928b52d828a56910e281b43a898a7b1eaed" + "sha": "8e05bab4b7907c14947287d46aa7d89ac93925d4", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Clipboard-PreviewMarkdown.ps1", @@ -59,7 +69,9 @@ "icon": "๐Ÿ‘๏ธ", "author": "phireForge", "tags": [], - "sha": "f81ef43f33c2e7adf655631f2a5b9324096196ae" + "sha": "53ba875882efadc2418b22d50e915d14a6c97573", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Flush-DNS.ps1", @@ -69,7 +81,9 @@ "icon": "๐Ÿงน", "author": "phireForge", "tags": [], - "sha": "a6bd75fa562bd2b931283cde4f7e1c4256d34ec3" + "sha": "890907771ba92ff55e9f39051aad3b877961095b", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Generate-Password.ps1", @@ -79,7 +93,9 @@ "icon": "๐Ÿ”‘", "author": "phireForge", "tags": [], - "sha": "700386ea9d8a178b086eb818dc608e3306383ce5" + "sha": "953bf2951cdaa7645db3c63b06693e5daab05c8a", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/List-Processes.ps1", @@ -89,7 +105,9 @@ "icon": "๐Ÿ“‹", "author": "phireForge", "tags": [], - "sha": "a46be27b66f20ccc30f5be309ceaa9f5d92b479f" + "sha": "d036fab6f7e35c39636242d54d080c0d79f5f701", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/Stop-Process.ps1", @@ -99,7 +117,9 @@ "icon": "๐Ÿ›‘", "author": "phireForge", "tags": [], - "sha": "ca75e5ba90d610bbd910ea6409f0210d9b3eb3e7" + "sha": "7bc6fb9a0ed61ac07896b6e43ba7a84cc37926a8", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/View-CertificateExpiry.ps1", @@ -109,7 +129,9 @@ "icon": "๐Ÿ”", "author": "phireForge", "tags": [], - "sha": "5d82f03ba21fb7d068f75bd0e08e12816b7fe5c3" + "sha": "01868aa8bd6b9905c73aecb51fdd9d261574b339", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/View-DiskSpace.ps1", @@ -119,7 +141,9 @@ "icon": "๐Ÿ’พ", "author": "phireForge", "tags": [], - "sha": "2189cc18708b2114e048e89a3c75ea14fa2a3dca" + "sha": "96b2ca373a2e5b77996aa922dc7a92f3bb77a811", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/View-EnvironmentVariables.ps1", @@ -129,7 +153,9 @@ "icon": "๐ŸŒฑ", "author": "phireForge", "tags": [], - "sha": "96d69fb1733d0f20cb76ac55203ae72afd5f36bf" + "sha": "dd7602333fd59cfdb0aca1419a3bfa19f4e0d698", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/View-EventLogErrors.ps1", @@ -139,7 +165,9 @@ "icon": "๐Ÿšจ", "author": "phireForge", "tags": [], - "sha": "d522b31c5c2066c0a4be39892f5b9435fe943d14" + "sha": "f908e8f01e9fad17bd05d0992c1a32302d7ee6da", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/View-SystemStatistics.ps1", @@ -149,7 +177,9 @@ "icon": "๐Ÿ“Š", "author": "phireForge", "tags": [], - "sha": "0f2f35ba65d1aaa883c544b76e9cf4518d98ea56" + "sha": "1bad0d6c52c5a0185efb44b4212845f742b34b2d", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" }, { "path": "scripts/phireForge/View-TimeZones.ps1", @@ -159,7 +189,9 @@ "icon": "๐Ÿ•’", "author": "phireForge", "tags": [], - "sha": "3b6430eca549f609bbdb13066411f04e53fcecec" + "sha": "9e3a1ad3f03053eddcb90316506382bae3a47aa5", + "version": "1.0.0", + "lastModified": "2026-07-04T06:30:07-04:00" } ] } diff --git a/scripts/phireForge/CheatSheet-Markdown.ps1 b/scripts/phireForge/CheatSheet-Markdown.ps1 index 1545d55..0021089 100644 --- a/scripts/phireForge/CheatSheet-Markdown.ps1 +++ b/scripts/phireForge/CheatSheet-Markdown.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Show a polished Markdown reference with syntax examples, rendered output, and practical tips #> -[ScriptHost('pwsh')] [ScriptGroup('Reference')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ“')] [ScriptTimeout(5000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/CheatSheet-RegularExpressions.ps1 b/scripts/phireForge/CheatSheet-RegularExpressions.ps1 index 6aa0f5d..8fdc369 100644 --- a/scripts/phireForge/CheatSheet-RegularExpressions.ps1 +++ b/scripts/phireForge/CheatSheet-RegularExpressions.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Show a practical regular-expression reference with tokens, flags, recipes, and PowerShell examples #> -[ScriptHost('pwsh')] [ScriptGroup('Reference')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ”ค')] [ScriptTimeout(5000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/Clipboard-LinesToCsv.ps1 b/scripts/phireForge/Clipboard-LinesToCsv.ps1 index 93b296f..2ad5803 100644 --- a/scripts/phireForge/Clipboard-LinesToCsv.ps1 +++ b/scripts/phireForge/Clipboard-LinesToCsv.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Take multi-line text from the clipboard and join it into a single comma-delimited line #> -[ScriptHost('pwsh')] [ScriptGroup('Text')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ”—')] [ScriptTimeout(5000)] [ScriptOutput('Clipboard')] diff --git a/scripts/phireForge/Clipboard-LinesToDoubleQuotedCsv.ps1 b/scripts/phireForge/Clipboard-LinesToDoubleQuotedCsv.ps1 index 251eb09..0407711 100644 --- a/scripts/phireForge/Clipboard-LinesToDoubleQuotedCsv.ps1 +++ b/scripts/phireForge/Clipboard-LinesToDoubleQuotedCsv.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Take multi-line text from the clipboard, wrap each line in double quotes, and join into one comma-delimited line #> -[ScriptHost('pwsh')] [ScriptGroup('Text')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ”—')] [ScriptTimeout(5000)] [ScriptOutput('Clipboard')] diff --git a/scripts/phireForge/Clipboard-LinesToSingleQuoteCsv.ps1 b/scripts/phireForge/Clipboard-LinesToSingleQuoteCsv.ps1 index 4aa5592..8e05bab 100644 --- a/scripts/phireForge/Clipboard-LinesToSingleQuoteCsv.ps1 +++ b/scripts/phireForge/Clipboard-LinesToSingleQuoteCsv.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Take multi-line text from the clipboard, wrap each line in single quotes, and join into one comma-delimited line #> -[ScriptHost('pwsh')] [ScriptGroup('Text')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ”—')] [ScriptTimeout(5000)] [ScriptOutput('Clipboard')] diff --git a/scripts/phireForge/Clipboard-PreviewMarkdown.ps1 b/scripts/phireForge/Clipboard-PreviewMarkdown.ps1 index f81ef43..53ba875 100644 --- a/scripts/phireForge/Clipboard-PreviewMarkdown.ps1 +++ b/scripts/phireForge/Clipboard-PreviewMarkdown.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Take Markdown text from the clipboard and display it rendered in Markdown mode #> -[ScriptHost('pwsh')] [ScriptGroup('Text')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ‘๏ธ')] [ScriptTimeout(5000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/Flush-DNS.ps1 b/scripts/phireForge/Flush-DNS.ps1 index a6bd75f..8909077 100644 --- a/scripts/phireForge/Flush-DNS.ps1 +++ b/scripts/phireForge/Flush-DNS.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Clear the Windows DNS resolver cache so the next lookups hit DNS fresh #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿงน')] [ScriptTimeout(10000)] [ScriptOutput('Toast')] diff --git a/scripts/phireForge/Generate-Password.ps1 b/scripts/phireForge/Generate-Password.ps1 index 700386e..953bf29 100644 --- a/scripts/phireForge/Generate-Password.ps1 +++ b/scripts/phireForge/Generate-Password.ps1 @@ -21,8 +21,8 @@ using module .\PaletteScriptAttributes.psm1 .\Password-Generator.ps1 -Length 20 -Symbols:$true Generate a 20-character password that includes symbols #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ”‘')] [ScriptTimeout(5000)] [ScriptOutput('Clipboard')] diff --git a/scripts/phireForge/List-Processes.ps1 b/scripts/phireForge/List-Processes.ps1 index a46be27..d036fab 100644 --- a/scripts/phireForge/List-Processes.ps1 +++ b/scripts/phireForge/List-Processes.ps1 @@ -8,8 +8,8 @@ using module .\PaletteScriptAttributes.psm1 .PARAMETER Filter Optional text to match against the process name #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ“‹')] [ScriptTimeout(15000)] [ScriptOutput('List')] diff --git a/scripts/phireForge/Stop-Process.ps1 b/scripts/phireForge/Stop-Process.ps1 index ca75e5b..7bc6fb9 100644 --- a/scripts/phireForge/Stop-Process.ps1 +++ b/scripts/phireForge/Stop-Process.ps1 @@ -14,8 +14,8 @@ using module .\PaletteScriptAttributes.psm1 .\Stop-Process.ps1 -Target notepad Terminate every process named notepad #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ›‘')] [ScriptTimeout(10000)] [ScriptOutput('Toast')] diff --git a/scripts/phireForge/View-CertificateExpiry.ps1 b/scripts/phireForge/View-CertificateExpiry.ps1 index 5d82f03..01868aa 100644 --- a/scripts/phireForge/View-CertificateExpiry.ps1 +++ b/scripts/phireForge/View-CertificateExpiry.ps1 @@ -16,8 +16,8 @@ using module .\PaletteScriptAttributes.psm1 .PARAMETER MaxCertificates Maximum number of certificates to display #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ”')] [ScriptTimeout(20000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/View-DiskSpace.ps1 b/scripts/phireForge/View-DiskSpace.ps1 index 2189cc1..96b2ca3 100644 --- a/scripts/phireForge/View-DiskSpace.ps1 +++ b/scripts/phireForge/View-DiskSpace.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Show disk space statistics for all drives, rendered as Markdown #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ’พ')] [ScriptTimeout(15000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/View-EnvironmentVariables.ps1 b/scripts/phireForge/View-EnvironmentVariables.ps1 index 96d69fb..dd76023 100644 --- a/scripts/phireForge/View-EnvironmentVariables.ps1 +++ b/scripts/phireForge/View-EnvironmentVariables.ps1 @@ -6,8 +6,8 @@ using module .\PaletteScriptAttributes.psm1 .DESCRIPTION Dump the current environment variables as a fenced Markdown code block #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐ŸŒฑ')] [ScriptTimeout(10000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/View-EventLogErrors.ps1 b/scripts/phireForge/View-EventLogErrors.ps1 index d522b31..f908e8f 100644 --- a/scripts/phireForge/View-EventLogErrors.ps1 +++ b/scripts/phireForge/View-EventLogErrors.ps1 @@ -12,8 +12,8 @@ using module .\PaletteScriptAttributes.psm1 .PARAMETER IncludeWarnings Include warning events along with errors #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿšจ')] [ScriptTimeout(20000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/View-SystemStatistics.ps1 b/scripts/phireForge/View-SystemStatistics.ps1 index 0f2f35b..1bad0d6 100644 --- a/scripts/phireForge/View-SystemStatistics.ps1 +++ b/scripts/phireForge/View-SystemStatistics.ps1 @@ -8,8 +8,8 @@ using module .\PaletteScriptAttributes.psm1 .PARAMETER TopProcesses Number of memory-heavy processes to include #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ“Š')] [ScriptTimeout(20000)] [ScriptOutput('Markdown')] diff --git a/scripts/phireForge/View-TimeZones.ps1 b/scripts/phireForge/View-TimeZones.ps1 index 3b6430e..9e3a1ad 100644 --- a/scripts/phireForge/View-TimeZones.ps1 +++ b/scripts/phireForge/View-TimeZones.ps1 @@ -10,8 +10,8 @@ using module .\PaletteScriptAttributes.psm1 .PARAMETER Use24HourTime Display times in 24-hour format #> -[ScriptHost('pwsh')] [ScriptGroup('Utilities')] +[ScriptVersion('1.0.0')] [ScriptIcon('๐Ÿ•’')] [ScriptTimeout(10000)] [ScriptOutput('Markdown')] diff --git a/tools/Build-Index.ps1 b/tools/Build-Index.ps1 index ec7a365..570ecd7 100644 --- a/tools/Build-Index.ps1 +++ b/tools/Build-Index.ps1 @@ -89,11 +89,12 @@ function Get-ScriptMetadata { $title = if ($help.Synopsis) { $help.Synopsis } else { [System.IO.Path]::GetFileNameWithoutExtension($Path) } $description = $help.Description - # [ScriptGroup(...)] / [ScriptIcon(...)] live on the param block's attribute list, ahead - # of param() itself - read them straight off the AST rather than executing anything. - # ParamBlock is null for a script with no param() at all. + # [ScriptGroup(...)] / [ScriptIcon(...)] / [ScriptVersion(...)] live on the param block's + # attribute list, ahead of param() itself - read them straight off the AST rather than + # executing anything. ParamBlock is null for a script with no param() at all. $group = $null $icon = $null + $version = $null if ($ast.ParamBlock) { foreach ($attributeAst in $ast.ParamBlock.Attributes) { @@ -105,15 +106,24 @@ function Get-ScriptMetadata { switch ($attributeAst.TypeName.Name) { 'ScriptGroup' { $group = $firstArg.Value } 'ScriptIcon' { $icon = $firstArg.Value } + 'ScriptVersion' { $version = $firstArg.Value } } } } + # A script that omits [ScriptVersion(...)] is assumed to be at the baseline version rather + # than having "no version" - keeps every catalog entry comparable instead of the Store + # having to special-case a missing field. + if (-not $version) { + $version = '1.0.0' + } + [pscustomobject]@{ Title = $title Description = $description Group = $group Icon = $icon + Version = $version } } @@ -141,15 +151,25 @@ Get-ChildItem -Path $scriptsRoot -Directory | ForEach-Object { $sha = (git -C $RepoRoot hash-object $scriptFile.FullName).Trim() + # Commit date of the most recent commit that touched this file - i.e. what GitHub's + # own "Latest commit" file history shows. Requires full history (not a shallow clone); + # on a depth-1 checkout every file would falsely show the same single available commit. + # $null (rather than empty output) when the file has no commits yet, e.g. it's staged + # but not committed - `git log` prints nothing rather than an empty line. + $lastModifiedRaw = git -C $RepoRoot log -1 --format=%cI -- $relativePath + $lastModified = if ($lastModifiedRaw) { $lastModifiedRaw.Trim() } else { $null } + $entries.Add([ordered]@{ - path = $relativePath - title = $meta.Title - description = $meta.Description - group = $meta.Group - icon = $meta.Icon - author = $author - tags = @() - sha = $sha + path = $relativePath + title = $meta.Title + description = $meta.Description + group = $meta.Group + icon = $meta.Icon + author = $author + tags = @() + sha = $sha + version = $meta.Version + lastModified = $lastModified }) } } diff --git a/tools/Test-Contribution.ps1 b/tools/Test-Contribution.ps1 index 2940a49..b930a58 100644 --- a/tools/Test-Contribution.ps1 +++ b/tools/Test-Contribution.ps1 @@ -159,10 +159,9 @@ foreach ($authorFolder in $authorFolders) { } } - if (-not $attributes.ContainsKey('ScriptHost')) { - Add-Failure -Path $relativePath -Message 'Missing [ScriptHost(''pwsh''|''powershell'')].' - } - elseif ($AllowedHosts -notcontains $attributes['ScriptHost']) { + # [ScriptHost(...)] is optional - PaletteShell falls back to the user's configured + # default host when it's omitted, so only validate the value when it's present. + if ($attributes.ContainsKey('ScriptHost') -and $AllowedHosts -notcontains $attributes['ScriptHost']) { Add-Failure -Path $relativePath -Message "Invalid ScriptHost '$($attributes['ScriptHost'])'. Expected one of: $($AllowedHosts -join ', ')." }