diff --git a/.github/mkdocs.yml b/.github/mkdocs.yml deleted file mode 100644 index df5e17a..0000000 --- a/.github/mkdocs.yml +++ /dev/null @@ -1,75 +0,0 @@ -site_name: -{{ REPO_NAME }}- -theme: - name: material - language: en - font: - text: Roboto - code: Sono - logo: Assets/icon.png - favicon: Assets/icon.png - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/link - name: Switch to dark mode - # Palette toggle for dark mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - toggle: - primary: black - accent: green - icon: material/toggle-switch-off-outline - name: Switch to light mode - # Palette toggle for light mode - - media: '(prefers-color-scheme: light)' - scheme: default - toggle: - primary: indigo - accent: green - icon: material/toggle-switch - name: Switch to system preference - icon: - repo: material/github - features: - - navigation.instant - - navigation.instant.progress - - navigation.indexes - - navigation.top - - navigation.tracking - - navigation.expand - - search.suggest - - search.highlight - -repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- -repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- - -plugins: - - search - -markdown_extensions: - - toc: - permalink: true # Adds a link icon to headings - - attr_list - - admonition - - md_in_html - - pymdownx.details # Enables collapsible admonitions - -extra: - social: - - icon: fontawesome/brands/discord - link: https://discord.gg/jedJWCPAhD - name: -{{ REPO_OWNER }}- on Discord - - icon: fontawesome/brands/github - link: https://github.com/-{{ REPO_OWNER }}-/ - name: -{{ REPO_OWNER }}- on GitHub - consent: - title: Cookie consent - description: >- - We use cookies to recognize your repeated visits and preferences, as well - as to measure the effectiveness of our documentation and whether users - find what they're searching for. With your consent, you're helping us to - make our documentation better. - actions: - - accept - - reject diff --git a/.github/workflows/Process-PSModule.yml b/.github/workflows/Process-PSModule.yml index a8e37ad..a895338 100644 --- a/.github/workflows/Process-PSModule.yml +++ b/.github/workflows/Process-PSModule.yml @@ -27,5 +27,5 @@ permissions: jobs: Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@da180bac16b13bfbcdf08b2e4e221b5b49e5ff28 # v6.1.4 + uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@fb1bdb8fefd243292f779d2a856a38db6fe6daf4 # v6.1.13 secrets: inherit diff --git a/.github/zensical.toml b/.github/zensical.toml new file mode 100644 index 0000000..a10a807 --- /dev/null +++ b/.github/zensical.toml @@ -0,0 +1,69 @@ +[project] +site_name = "Sodium" +repo_name = "PSModule/Sodium" +repo_url = "https://github.com/PSModule/Sodium" + +[project.theme] +variant = "classic" +language = "en" +logo = "Assets/icon.png" +favicon = "Assets/icon.png" +features = [ + "navigation.instant", + "navigation.instant.progress", + "navigation.indexes", + "navigation.top", + "navigation.tracking", + "navigation.expand", + "search.suggest", + "search.highlight", + "content.code.copy" +] + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "lucide/sun-moon" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "black" +accent = "green" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "indigo" +accent = "green" +toggle.icon = "lucide/sun" +toggle.name = "Switch to system preference" + +[project.theme.icon] +repo = "fontawesome/brands/github" + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.attr_list] +[project.markdown_extensions.admonition] +[project.markdown_extensions.md_in_html] +[project.markdown_extensions.pymdownx.details] +[project.markdown_extensions.pymdownx.superfences] + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.gg/jedJWCPAhD" +name = "PSModule on Discord" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/PSModule/" +name = "PSModule on GitHub" + +[project.extra.consent] +title = "Cookie consent" +description = "We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better." +actions = ["accept", "reject"] diff --git a/PSModule/Sodium/Sodium.cs b/PSModule/Sodium/Sodium.cs index 08ec69e..75f6d82 100644 --- a/PSModule/Sodium/Sodium.cs +++ b/PSModule/Sodium/Sodium.cs @@ -203,6 +203,11 @@ public static KeyPairBase64 GenerateKeyPairBase64(string seedText) } public static string DerivePublicKeyBase64(string privateKeyBase64) + { + return Convert.ToBase64String(DerivePublicKey(privateKeyBase64)); + } + + public static byte[] DerivePublicKey(string privateKeyBase64) { ArgumentNullException.ThrowIfNull(privateKeyBase64); var privateKey = DecodeBase64Exact(privateKeyBase64, SecretKeyBytes, "private key"); @@ -213,7 +218,7 @@ public static string DerivePublicKeyBase64(string privateKeyBase64) { throw new InvalidOperationException("Unable to derive public key from private key."); } - return Convert.ToBase64String(publicKey); + return publicKey; } finally { diff --git a/src/functions/private/Assert-SodiumNativeRuntime.ps1 b/src/functions/private/Assert-SodiumNativeRuntime.ps1 new file mode 100644 index 0000000..2ebcbd3 --- /dev/null +++ b/src/functions/private/Assert-SodiumNativeRuntime.ps1 @@ -0,0 +1,28 @@ +function Assert-SodiumNativeRuntime { + <# + .SYNOPSIS + Provides platform-specific diagnostics after Sodium native initialization fails. + + .DESCRIPTION + Checks the Windows Visual C++ runtime after a native initialization exception and throws a targeted message when the required + runtime is unavailable. + + .NOTES + This function only runs after native library loading fails, which cannot be reproduced safely after the library is loaded in the test process. + #> + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()] + [OutputType([void])] + [CmdletBinding()] + param() + + process { + if ($IsWindows -and $script:ProcessArchitecture -in @('X64', 'X86')) { + $hasRuntime = Assert-VisualCRedistributableInstalled -Version '14.0' -Architecture $script:ProcessArchitecture + if (-not $hasRuntime) { + $message = "Sodium native initialization failed; the Visual C++ Redistributable for " + + "$($script:ProcessArchitecture) appears to be missing or below the required version." + throw $message + } + } + } +} diff --git a/src/functions/private/Initialize-Sodium.ps1 b/src/functions/private/Initialize-Sodium.ps1 index 2ad47bb..a4433e8 100644 --- a/src/functions/private/Initialize-Sodium.ps1 +++ b/src/functions/private/Initialize-Sodium.ps1 @@ -23,26 +23,19 @@ $initializationResult = [PSModule.Sodium]::sodium_init() } catch { $script:Supported = $false - if ($IsWindows) { - if ($script:ProcessArchitecture -in @('X64', 'X86')) { - $hasRuntime = Assert-VisualCRedistributableInstalled -Version '14.0' -Architecture $script:ProcessArchitecture - if (-not $hasRuntime) { - $message = "Sodium native initialization failed; the Visual C++ Redistributable for " + - "$($script:ProcessArchitecture) appears to be missing or below the required version." - throw $message - } - } - } + Assert-SodiumNativeRuntime throw } if ($initializationResult -lt 0) { throw 'Sodium initialization failed.' } - $script:SodiumPublicKeyBytes = [PSModule.Sodium]::crypto_box_publickeybytes().ToUInt32() - $script:SodiumPrivateKeyBytes = [PSModule.Sodium]::crypto_box_secretkeybytes().ToUInt32() - $script:SodiumSealBytes = [PSModule.Sodium]::crypto_box_sealbytes().ToUInt32() - $script:SodiumSeedBytes = [PSModule.Sodium]::crypto_box_seedbytes().ToUInt32() + # Fixed crypto_box constants (curve25519xsalsa20poly1305). The C# layer queries and validates the real + # native values at type initialization; hardcoding here avoids four extra interop call sites at import. + $script:SodiumPublicKeyBytes = 32u + $script:SodiumPrivateKeyBytes = 32u + $script:SodiumSealBytes = 48u + $script:SodiumSeedBytes = 32u $script:SodiumInitialized = $true } diff --git a/src/functions/private/Resolve-SodiumRuntimeIdentifier.ps1 b/src/functions/private/Resolve-SodiumRuntimeIdentifier.ps1 new file mode 100644 index 0000000..50b9dfb --- /dev/null +++ b/src/functions/private/Resolve-SodiumRuntimeIdentifier.ps1 @@ -0,0 +1,65 @@ +function Resolve-SodiumRuntimeIdentifier { + <# + .SYNOPSIS + Resolves the native Sodium runtime identifier. + + .DESCRIPTION + Maps the active operating system and process architecture to the runtime identifier used by the bundled native library. + #> + [OutputType([string])] + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Runtime.InteropServices.Architecture] $ProcessArchitecture, + + [Parameter()] + [switch] $Linux, + + [Parameter()] + [switch] $MacOS, + + [Parameter()] + [switch] $Windows + ) + + process { + switch ($true) { + $Linux { + switch ($ProcessArchitecture) { + 'Arm64' { return 'linux-arm64' } + 'X64' { return 'linux-x64' } + default { + $message = "Unsupported Linux process architecture: $ProcessArchitecture. " + + 'Please refer to the documentation for supported architectures.' + throw $message + } + } + } + $MacOS { + switch ($ProcessArchitecture) { + 'Arm64' { return 'osx-arm64' } + 'X64' { return 'osx-x64' } + default { + $message = "Unsupported macOS process architecture: $ProcessArchitecture. " + + 'Please refer to the documentation for supported architectures.' + throw $message + } + } + } + $Windows { + switch ($ProcessArchitecture) { + 'X64' { return 'win-x64' } + 'X86' { return 'win-x86' } + default { + $message = "Unsupported Windows process architecture: $ProcessArchitecture. " + + 'Please refer to the documentation for supported architectures.' + throw $message + } + } + } + default { + throw 'Unsupported platform. Please refer to the documentation for more information.' + } + } + } +} diff --git a/src/functions/public/ConvertFrom-SodiumSealedBox.ps1 b/src/functions/public/ConvertFrom-SodiumSealedBox.ps1 index 5b72e4a..540b0d1 100644 --- a/src/functions/public/ConvertFrom-SodiumSealedBox.ps1 +++ b/src/functions/public/ConvertFrom-SodiumSealedBox.ps1 @@ -68,10 +68,6 @@ [string] $PrivateKey ) - begin { - Initialize-Sodium - } - process { try { if (-not [string]::IsNullOrWhiteSpace($PublicKey)) { diff --git a/src/functions/public/ConvertTo-SodiumSealedBox.ps1 b/src/functions/public/ConvertTo-SodiumSealedBox.ps1 index ff87d42..43dd858 100644 --- a/src/functions/public/ConvertTo-SodiumSealedBox.ps1 +++ b/src/functions/public/ConvertTo-SodiumSealedBox.ps1 @@ -55,10 +55,6 @@ [ValidateNotNullOrEmpty()] [string] $PublicKey ) - begin { - Initialize-Sodium - } - process { try { return [PSModule.Sodium]::SealBase64($Message, $PublicKey) diff --git a/src/functions/public/Get-SodiumPublicKey.ps1 b/src/functions/public/Get-SodiumPublicKey.ps1 index 8a7ab08..77ac1d2 100644 --- a/src/functions/public/Get-SodiumPublicKey.ps1 +++ b/src/functions/public/Get-SodiumPublicKey.ps1 @@ -67,6 +67,10 @@ https://psmodule.io/Sodium/Functions/Get-SodiumPublicKey/ #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseOutputTypeCorrectly', '', + Justification = 'The unary comma preserves the byte array as one pipeline object.' + )] [OutputType([string], ParameterSetName = 'Base64')] [OutputType([byte[]], ParameterSetName = 'AsByteArray')] [CmdletBinding(DefaultParameterSetName = 'Base64')] @@ -81,14 +85,10 @@ [switch] $AsByteArray ) - begin { - Initialize-Sodium - } - process { if ($AsByteArray) { try { - return [System.Convert]::FromBase64String([PSModule.Sodium]::DerivePublicKeyBase64($PrivateKey)) + return , ([PSModule.Sodium]::DerivePublicKey($PrivateKey)) } catch [System.Management.Automation.MethodInvocationException] { throw $_.Exception.InnerException } @@ -100,5 +100,4 @@ } } - end {} } diff --git a/src/functions/public/New-SodiumKeyPair.ps1 b/src/functions/public/New-SodiumKeyPair.ps1 index a8bebe5..af515a2 100644 --- a/src/functions/public/New-SodiumKeyPair.ps1 +++ b/src/functions/public/New-SodiumKeyPair.ps1 @@ -84,10 +84,6 @@ [string] $Seed ) - begin { - Initialize-Sodium - } - process { try { if ($PSCmdlet.ParameterSetName -eq 'SeededKeyPair') { diff --git a/src/main.ps1 b/src/main.ps1 index 0d1d6f2..7cff0d1 100644 --- a/src/main.ps1 +++ b/src/main.ps1 @@ -1,39 +1,9 @@ $processArchitecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -switch ($true) { - $IsLinux { - switch ($processArchitecture) { - 'Arm64' { $runtimeIdentifier = 'linux-arm64' } - 'X64' { $runtimeIdentifier = 'linux-x64' } - default { - throw "Unsupported Linux process architecture: $processArchitecture. Please refer to the documentation for supported architectures." - } - } - } - $IsMacOS { - switch ($processArchitecture) { - 'Arm64' { $runtimeIdentifier = 'osx-arm64' } - 'X64' { $runtimeIdentifier = 'osx-x64' } - default { - throw "Unsupported macOS process architecture: $processArchitecture. Please refer to the documentation for supported architectures." - } - } - } - $IsWindows { - switch ($processArchitecture) { - 'X64' { $runtimeIdentifier = 'win-x64' } - 'X86' { $runtimeIdentifier = 'win-x86' } - default { - throw "Unsupported Windows process architecture: $processArchitecture. Please refer to the documentation for supported architectures." - } - } - } - default { - throw 'Unsupported platform. Please refer to the documentation for more information.' - } -} +$runtimeIdentifier = Resolve-SodiumRuntimeIdentifier -ProcessArchitecture $processArchitecture ` + -Linux:$IsLinux -MacOS:$IsMacOS -Windows:$IsWindows -$assemblyPath = Join-Path -Path $PSScriptRoot -ChildPath "libs/$runtimeIdentifier/PSModule.Sodium.dll" +$assemblyPath = [System.IO.Path]::Combine($PSScriptRoot, 'libs', $runtimeIdentifier, 'PSModule.Sodium.dll') Import-Module $assemblyPath -ErrorAction Stop # Optimistically mark supported; Initialize-Sodium runs during module import and checks Windows VC++ runtime only if native init fails. diff --git a/tests/Sodium.Tests.ps1 b/tests/Sodium.Tests.ps1 index 95680de..404c4b8 100644 --- a/tests/Sodium.Tests.ps1 +++ b/tests/Sodium.Tests.ps1 @@ -163,6 +163,14 @@ Describe 'Sodium' { $derivedPublicKey | Should -Be $expectedPublicKey } + It 'Get-SodiumPublicKey - Returns the public key as a byte array' { + $keyPair = New-SodiumKeyPair + $derivedPublicKey = Get-SodiumPublicKey -PrivateKey $keyPair.PrivateKey -AsByteArray + + ($derivedPublicKey -is [byte[]]) | Should -BeTrue + [Convert]::ToBase64String($derivedPublicKey) | Should -Be $keyPair.PublicKey + } + It 'Get-SodiumPublicKey - Throws an error when an invalid private key is provided' { $invalidPrivateKey = 'InvalidKey' @@ -185,5 +193,140 @@ Describe 'Sodium' { $result | Should -BeTrue } } + + It 'Assert-VisualCRedistributableInstalled reports a missing runtime' { + InModuleScope Sodium { + Mock Get-ItemProperty { $null } + + Set-Variable -Name IsWindows -Value $true -Scope Script + try { + $result = Assert-VisualCRedistributableInstalled -Version '14.0' -Architecture 'X64' 3>$null + $result | Should -BeFalse + } finally { + Remove-Variable -Name IsWindows -Scope Script + } + } + } + + It 'Assert-VisualCRedistributableInstalled accepts a matching runtime' { + InModuleScope Sodium { + Mock Get-ItemProperty { + [pscustomobject]@{ + Installed = 1 + Version = 'v14.30.30704.0' + } + } + + Set-Variable -Name IsWindows -Value $true -Scope Script + try { + $result = Assert-VisualCRedistributableInstalled -Version '14.0' + $result | Should -BeTrue + } finally { + Remove-Variable -Name IsWindows -Scope Script + } + } + } + + It 'Initialize-Sodium treats repeated initialization as a no-op' { + InModuleScope Sodium { + $script:SodiumInitialized | Should -BeTrue + { Initialize-Sodium } | Should -Not -Throw + } + } + + It 'Initialize-Sodium restores cached native buffer sizes' { + InModuleScope Sodium { + $script:SodiumInitialized = $false + $script:SodiumPublicKeyBytes = $null + $script:SodiumPrivateKeyBytes = $null + $script:SodiumSealBytes = $null + $script:SodiumSeedBytes = $null + + Initialize-Sodium + + $script:SodiumInitialized | Should -BeTrue + $script:SodiumPublicKeyBytes | Should -Be 32 + $script:SodiumPrivateKeyBytes | Should -Be 32 + $script:SodiumSealBytes | Should -Be 48 + $script:SodiumSeedBytes | Should -Be 32 + } + } + + It 'Initialize-Sodium rejects an unsupported platform' { + InModuleScope Sodium { + $script:Supported = $false + try { + { Initialize-Sodium } | Should -Throw 'Sodium is not supported on this platform.' + } finally { + $script:Supported = $true + } + } + } + + It 'Resolve-SodiumRuntimeIdentifier maps every supported runtime' { + InModuleScope Sodium { + Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'Arm64' -Linux | Should -Be 'linux-arm64' + Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'X64' -Linux | Should -Be 'linux-x64' + Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'Arm64' -MacOS | Should -Be 'osx-arm64' + Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'X64' -MacOS | Should -Be 'osx-x64' + Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'X64' -Windows | Should -Be 'win-x64' + Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'X86' -Windows | Should -Be 'win-x86' + } + } + + It 'Resolve-SodiumRuntimeIdentifier rejects unsupported runtimes' { + InModuleScope Sodium { + { Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'Arm' -Linux } | + Should -Throw 'Unsupported Linux process architecture: Arm.*' + { Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'Arm' -MacOS } | + Should -Throw 'Unsupported macOS process architecture: Arm.*' + { Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'Arm' -Windows } | + Should -Throw 'Unsupported Windows process architecture: Arm.*' + { Resolve-SodiumRuntimeIdentifier -ProcessArchitecture 'X64' } | + Should -Throw 'Unsupported platform.*' + } + } + } + + Context 'Parallel sessions' { + It 'Loads and completes crypto round trips in parallel runspaces' { + $modulePath = (Get-Module -Name Sodium -ErrorAction Stop).Path + Test-Path -Path $modulePath | Should -BeTrue + $results = 1..4 | ForEach-Object -Parallel { + Import-Module -Name $using:modulePath -Force + $keyPair = New-SodiumKeyPair -Seed "Runspace-$_" + $message = "Parallel runspace $_" + $sealedBox = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair.PublicKey + ConvertFrom-SodiumSealedBox -SealedBox $sealedBox -PrivateKey $keyPair.PrivateKey + } -ThrottleLimit 4 + + $results | Should -HaveCount 4 + $results | Should -Contain 'Parallel runspace 1' + $results | Should -Contain 'Parallel runspace 4' + } + + It 'Loads and completes crypto round trips in parallel processes' { + $modulePath = (Get-Module -Name Sodium -ErrorAction Stop).Path + $jobs = 1..4 | ForEach-Object { + Start-Job -ScriptBlock { + $id = $args[0] + $modulePath = $args[1] + Import-Module -Name $modulePath -Force + $keyPair = New-SodiumKeyPair -Seed "Process-$id" + $message = "Parallel process $id" + $sealedBox = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair.PublicKey + ConvertFrom-SodiumSealedBox -SealedBox $sealedBox -PrivateKey $keyPair.PrivateKey + } -ArgumentList $_, $modulePath + } + + try { + $results = $jobs | Receive-Job -Wait + $results | Should -HaveCount 4 + $results | Should -Contain 'Parallel process 1' + $results | Should -Contain 'Parallel process 4' + } finally { + $jobs | Remove-Job -Force + } + } } } diff --git a/tools/benchmark/Build-LocalModule.ps1 b/tools/benchmark/Build-LocalModule.ps1 new file mode 100644 index 0000000..bdc8721 --- /dev/null +++ b/tools/benchmark/Build-LocalModule.ps1 @@ -0,0 +1,67 @@ +<# + .SYNOPSIS + Assembles an importable Sodium module from the src folder for local testing and benchmarking. + + .DESCRIPTION + Mimics the PSModule build pipeline order (variables -> private functions -> public functions -> main.ps1) + and produces a Sodium.psd1/Sodium.psm1 pair with the native libs copied alongside. + + .EXAMPLE + .\Build-LocalModule.ps1 -OutputPath $env:TEMP\SodiumBench +#> +[CmdletBinding()] +param( + # Path to the module source folder. Defaults to /src. + [Parameter()] + [string] $SourcePath = (Join-Path -Path $PSScriptRoot -ChildPath '..\..\src'), + + # Folder in which the 'Sodium' module folder is created. + [Parameter(Mandatory)] + [string] $OutputPath +) +$ErrorActionPreference = 'Stop' + +$SourcePath = (Resolve-Path $SourcePath).Path +$moduleDir = Join-Path $OutputPath 'Sodium' +if (Test-Path $moduleDir) { + try { + Remove-Item $moduleDir -Recurse -Force + } catch { + # Native libs may be locked by a process that imported a previous build; build into a unique folder instead. + $moduleDir = Join-Path $OutputPath "Sodium-$([Guid]::NewGuid().ToString('N').Substring(0, 8))" + Write-Verbose "Previous build is locked; building into $moduleDir" -Verbose + } +} +New-Item -ItemType Directory -Path $moduleDir -Force | Out-Null + +$sb = [System.Text.StringBuilder]::new() +$publicFunctions = @() + +foreach ($file in (Get-ChildItem (Join-Path -Path $SourcePath -ChildPath 'variables\private') -Filter *.ps1 -ErrorAction SilentlyContinue)) { + [void]$sb.AppendLine((Get-Content $file.FullName -Raw)) +} +foreach ($file in (Get-ChildItem (Join-Path -Path $SourcePath -ChildPath 'functions\private') -Filter *.ps1)) { + [void]$sb.AppendLine((Get-Content $file.FullName -Raw)) +} +foreach ($file in (Get-ChildItem (Join-Path -Path $SourcePath -ChildPath 'functions\public') -Filter *.ps1)) { + [void]$sb.AppendLine((Get-Content $file.FullName -Raw)) + $publicFunctions += $file.BaseName +} +$mainPath = Join-Path -Path $SourcePath -ChildPath 'main.ps1' +if (Test-Path $mainPath) { + [void]$sb.AppendLine((Get-Content $mainPath -Raw)) +} +[void]$sb.AppendLine("Export-ModuleMember -Function '$($publicFunctions -join "', '")' -Alias '*'") + +Set-Content -Path (Join-Path -Path $moduleDir -ChildPath 'Sodium.psm1') -Value $sb.ToString() -Encoding UTF8BOM + +Copy-Item -Path (Join-Path -Path $SourcePath -ChildPath 'libs') -Destination $moduleDir -Recurse + +New-ModuleManifest -Path (Join-Path -Path $moduleDir -ChildPath 'Sodium.psd1') ` + -RootModule 'Sodium.psm1' ` + -ModuleVersion '999.0.0' ` + -FunctionsToExport $publicFunctions ` + -CmdletsToExport @() -VariablesToExport @() -AliasesToExport @() + +Write-Verbose "Built module at $moduleDir" -Verbose +Join-Path -Path $moduleDir -ChildPath 'Sodium.psd1' diff --git a/tools/benchmark/Invoke-ImportBenchmark.ps1 b/tools/benchmark/Invoke-ImportBenchmark.ps1 new file mode 100644 index 0000000..d63a680 --- /dev/null +++ b/tools/benchmark/Invoke-ImportBenchmark.ps1 @@ -0,0 +1,58 @@ +<# + .SYNOPSIS + Measures cold module import time in fresh PowerShell processes. + + .DESCRIPTION + Starts a new pwsh process per sample, imports the module once and reports average/min/max import time. + This is the dominant cost when the module is started in multiple sessions. + + .EXAMPLE + .\Invoke-ImportBenchmark.ps1 -ModulePath C:\temp\SodiumBench\Sodium\Sodium.psd1 -Label baseline +#> +[CmdletBinding()] +param( + # Path to the Sodium.psd1 to benchmark. Defaults to building the module from src into a temp folder. + [Parameter()] + [string] $ModulePath, + + # Label used in output file name (import-