feat: add custom shortcuts and multichannel packaging (#2) #43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| validate: | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # pinned action | |
| with: | |
| toolchain: 1.97.1 | |
| components: rustfmt, clippy | |
| - run: cargo fmt -- --check | |
| - run: cargo check --workspace --all-targets | |
| - run: cargo test | |
| - run: cargo clippy --all-targets -- -D warnings | |
| - run: cargo build --release | |
| - name: Install dependency policy tools | |
| shell: pwsh | |
| run: cargo install cargo-deny --version 0.20.2 --locked | |
| - run: cargo deny check | |
| powershell: | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Install pinned PowerShell quality tools | |
| shell: pwsh | |
| run: | | |
| Install-PSResource -Name PSScriptAnalyzer -Version 1.25.0 -Repository PSGallery -TrustRepository -Scope CurrentUser | |
| Install-PSResource -Name Pester -Version 6.1.0 -Repository PSGallery -TrustRepository -Scope CurrentUser | |
| - name: Parse and lint PowerShell | |
| shell: pwsh | |
| run: ./scripts/validate-powershell.ps1 | |
| - name: Run Pester tests | |
| shell: pwsh | |
| run: Invoke-Pester -Path ./tests/powershell -Output Detailed | |
| distribution: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node-version: ['22', '24', '26'] | |
| name: distribution (Node ${{ matrix.node-version }}) | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| - name: Enforce version consistency across channels | |
| # Version surfaces share one value; verify it once on the canonical | |
| # baseline leg instead of repeating it across the whole matrix. | |
| if: matrix.node-version == '24' | |
| shell: pwsh | |
| run: | | |
| $cargoVersion = (Get-Content Cargo.toml | Select-String '^version = "([0-9.]+)"$').Matches.Groups[1].Value | |
| $channels = @{ | |
| 'powershell/DevNav.psd1' = (Test-ModuleManifest -Path powershell/DevNav.psd1).Version.ToString() | |
| 'packaging/npm/package.json' = (Get-Content packaging/npm/package.json -Raw | ConvertFrom-Json).version | |
| 'packaging/scoop/devnav.template.json' = (Get-Content packaging/scoop/devnav.template.json -Raw | ConvertFrom-Json).version | |
| } | |
| foreach ($file in $channels.GetEnumerator()) { | |
| if ($file.Value -ne $cargoVersion) { | |
| throw "Version mismatch: Cargo.toml has $cargoVersion but $($file.Key) has $($file.Value). All channels must share one version." | |
| } | |
| } | |
| - name: Run the npm bootstrap unit tests | |
| run: node --test "tests/npm/**/*.test.mjs" | |
| - name: Validate the npm tarball allow-list | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $stage = Join-Path $env:RUNNER_TEMP 'devnav-pack-check' | |
| New-Item -ItemType Directory -Path "$stage\payload" -Force | Out-Null | |
| Copy-Item packaging\npm\package.json, packaging\npm\README.md, LICENSE $stage | |
| Copy-Item packaging\npm\bin $stage -Recurse | |
| Set-Content "$stage\payload\DevNavSetup-x64.exe" 'CI placeholder x64' | |
| Set-Content "$stage\payload\DevNavSetup-arm64.exe" 'CI placeholder arm64' | |
| $artifacts = [ordered]@{} | |
| foreach ($name in @('installer-x64', 'installer-arm64')) { | |
| $file = "DevNavSetup-$($name -replace 'installer-', '').exe" | |
| $artifacts[$name] = @{ | |
| file = $file | |
| sha256 = (Get-FileHash (Join-Path "$stage\payload" $file) -Algorithm SHA256).Hash.ToLowerInvariant() | |
| } | |
| } | |
| $packageVersion = (Get-Content packaging\npm\package.json -Raw | ConvertFrom-Json).version | |
| [ordered]@{ schemaVersion = 1; version = $packageVersion; artifacts = $artifacts } | | |
| ConvertTo-Json -Depth 5 | Set-Content "$stage\release-manifest.json" | |
| Push-Location $stage | |
| try { | |
| $dryRun = npm pack --dry-run --json | ConvertFrom-Json | |
| $files = @($dryRun[0].files | ForEach-Object { $_.path }) | Sort-Object | |
| $expected = @( | |
| 'LICENSE', 'README.md', 'bin/devnav.mjs', 'package.json', | |
| 'payload/DevNavSetup-arm64.exe', 'payload/DevNavSetup-x64.exe', | |
| 'release-manifest.json' | |
| ) | Sort-Object | |
| $extra = @($files | Where-Object { $_ -notin $expected }) | |
| $missing = @($expected | Where-Object { $_ -notin $files }) | |
| if ($files.Count -ne $expected.Count -or $extra.Count -gt 0 -or $missing.Count -gt 0) { | |
| throw "Tarball layout mismatch. Got [$($files -join ', ')]. Extra: $($extra -join ', '). Missing: $($missing -join ', ')." | |
| } | |
| Write-Host "Tarball allow-list verified: $($files.Count) files match exactly." | |
| } | |
| finally { | |
| Pop-Location | |
| } |