From 863e9278d60836efc0b47d939b0445d0c9f61c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:29:31 -0400 Subject: [PATCH 01/16] Add CI and release workflows --- .github/workflows/ci.yml | 112 +++++++++++ .github/workflows/release.yml | 187 ++++++++++++++++++ dotnet/Directory.Build.props | 15 ++ rust/crates/pinget-core/src/lib.rs | 9 +- .../fixtures/ManifestV1_28-Singleton.yaml | 13 ++ 5 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 dotnet/Directory.Build.props create mode 100644 rust/crates/pinget-core/tests/fixtures/ManifestV1_28-Singleton.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7e2cece --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,112 @@ +name: CI + +on: + push: + branches: + - '**' + tags-ignore: + - 'v*' + pull_request: + +permissions: + contents: read + +env: + DOTNET_NOLOGO: true + CARGO_TERM_COLOR: always + +jobs: + rust-lint: + name: Rust Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + + - name: Check formatting + run: cargo fmt --manifest-path rust/Cargo.toml --all --check + + - name: Run clippy + run: cargo clippy -q --manifest-path rust/Cargo.toml --workspace --tests -- -D warnings + + rust-build: + name: Rust Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + + - name: Test pinget-core + run: cargo test -p pinget-core --manifest-path rust/Cargo.toml + + - name: Test pinget-cli + run: cargo test -p pinget-cli --manifest-path rust/Cargo.toml + + - name: Build pinget-cli + run: cargo build -p pinget-cli --manifest-path rust/Cargo.toml + + dotnet: + name: Dotnet Build + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Install Pester + shell: pwsh + run: | + Set-PSRepository PSGallery -InstallationPolicy Trusted + Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck + + - name: Restore solution + run: dotnet restore dotnet/Devolutions.Pinget.slnx + + - name: Build solution + run: dotnet build dotnet/Devolutions.Pinget.slnx -c Release --no-restore + + - name: Run core tests + run: dotnet test dotnet/src/Devolutions.Pinget.Core.Tests/Devolutions.Pinget.Core.Tests.csproj -c Release --no-build + + - name: Run PowerShell tests + shell: pwsh + run: pwsh -NoLogo -NoProfile -File (Resolve-Path 'dotnet/tests/RunTests.ps1') + + - name: Validate NuGet packing + shell: pwsh + run: | + New-Item -Path artifacts/ci-nuget -ItemType Directory -Force | Out-Null + dotnet pack dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj -c Release --no-build -o artifacts/ci-nuget + dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj -c Release --no-build -o artifacts/ci-nuget + dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj -c Release --no-build -o artifacts/ci-nuget \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c65d45f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,187 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +env: + DOTNET_NOLOGO: true + CARGO_TERM_COLOR: always + +jobs: + prepare: + name: Prepare Version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + - name: Compute package version + id: version + shell: bash + run: | + VERSION="${GITHUB_REF_NAME#v}" + if [ -z "$VERSION" ]; then + echo "Tag version is empty" >&2 + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + rust-artifacts: + name: Rust Artifacts (${{ matrix.asset_name }}) + runs-on: ${{ matrix.os }} + needs: prepare + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + asset_name: pinget-windows-x64.zip + binary_path: rust/target/release/pinget.exe + - os: ubuntu-latest + asset_name: pinget-linux-x64.zip + binary_path: rust/target/release/pinget + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + + - name: Build release binary + run: cargo build -p pinget-cli --manifest-path rust/Cargo.toml --release + + - name: Archive Windows binary + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -Path dist -ItemType Directory -Force | Out-Null + Compress-Archive -LiteralPath '${{ matrix.binary_path }}' -DestinationPath "dist/${{ matrix.asset_name }}" -Force + + - name: Archive Unix binary + if: runner.os != 'Windows' + shell: bash + run: | + mkdir -p dist + zip -j "dist/${{ matrix.asset_name }}" "${{ matrix.binary_path }}" + + - name: Upload Rust artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.asset_name }} + path: dist/${{ matrix.asset_name }} + if-no-files-found: error + + csharp-packages: + name: C# Packages + runs-on: windows-latest + needs: prepare + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Install Pester + shell: pwsh + run: | + Set-PSRepository PSGallery -InstallationPolicy Trusted + Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck + + - name: Restore solution + run: dotnet restore dotnet/Devolutions.Pinget.slnx + + - name: Build solution + run: dotnet build dotnet/Devolutions.Pinget.slnx -c Release --no-restore + + - name: Run core tests + run: dotnet test dotnet/src/Devolutions.Pinget.Core.Tests/Devolutions.Pinget.Core.Tests.csproj -c Release --no-build + + - name: Run PowerShell tests + shell: pwsh + run: pwsh -NoLogo -NoProfile -File (Resolve-Path 'dotnet/tests/RunTests.ps1') + + - name: Pack NuGet packages + shell: pwsh + env: + PACKAGE_VERSION: ${{ needs.prepare.outputs.version }} + run: | + New-Item -Path dist/nuget -ItemType Directory -Force | Out-Null + $packArgs = @('-c', 'Release', '--no-build', '-p:ContinuousIntegrationBuild=true', "-p:Version=$env:PACKAGE_VERSION", '-p:IncludeSymbols=true', '-p:SymbolPackageFormat=snupkg', '-o', 'dist/nuget') + dotnet pack dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj @packArgs + dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj @packArgs + dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj @packArgs + + - name: Upload package artifacts + uses: actions/upload-artifact@v4 + with: + name: csharp-nuget + path: | + dist/nuget/*.nupkg + dist/nuget/*.snupkg + if-no-files-found: error + + github-release: + name: GitHub Release + runs-on: ubuntu-latest + needs: + - rust-artifacts + - csharp-packages + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist/release + merge-multiple: true + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: | + dist/release/*.zip + dist/release/*.nupkg + dist/release/*.snupkg + + publish-nuget: + name: Publish NuGet + runs-on: ubuntu-latest + needs: + - prepare + - csharp-packages + if: secrets.NUGET_API_KEY != '' + + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Download package artifacts + uses: actions/download-artifact@v4 + with: + name: csharp-nuget + path: dist/nuget + + - name: Push packages to NuGet.org + shell: bash + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + dotnet nuget push dist/nuget/*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push dist/nuget/*.snupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate \ No newline at end of file diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props new file mode 100644 index 0000000..6d59fcf --- /dev/null +++ b/dotnet/Directory.Build.props @@ -0,0 +1,15 @@ + + + Devolutions + Devolutions Inc. + MIT + https://github.com/Devolutions/pinget + https://github.com/Devolutions/pinget.git + git + true + false + pinget;winget;package-manager;powershell;dotnet + See the repository changelog and release notes for details. + Copyright 2021-2026 Devolutions Inc. + + \ No newline at end of file diff --git a/rust/crates/pinget-core/src/lib.rs b/rust/crates/pinget-core/src/lib.rs index 045432b..150f992 100644 --- a/rust/crates/pinget-core/src/lib.rs +++ b/rust/crates/pinget-core/src/lib.rs @@ -6199,13 +6199,8 @@ Installers: #[test] fn parses_fixture_manifest() { let root = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("..") - .join("..") - .join("src") - .join("AppInstallerCLITests") - .join("TestData") + .join("tests") + .join("fixtures") .join("ManifestV1_28-Singleton.yaml"); let bytes = fs::read(root).expect("fixture bytes"); let manifest = parse_yaml_manifest(&bytes).expect("manifest"); diff --git a/rust/crates/pinget-core/tests/fixtures/ManifestV1_28-Singleton.yaml b/rust/crates/pinget-core/tests/fixtures/ManifestV1_28-Singleton.yaml new file mode 100644 index 0000000..24d2cba --- /dev/null +++ b/rust/crates/pinget-core/tests/fixtures/ManifestV1_28-Singleton.yaml @@ -0,0 +1,13 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +PackageName: MSIX SDK +Publisher: Microsoft +ShortDescription: Test fixture manifest for pinget-core parsing. +ManifestType: singleton +ManifestVersion: 1.10.0 +Installers: + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://example.test/msixsdk-x64.exe + InstallerSha256: 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF \ No newline at end of file From 334ba1ff8386795140b99e328cb9a14e313d8be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:34:30 -0400 Subject: [PATCH 02/16] Fix workflow startup issues --- .github/workflows/ci.yml | 2 -- .github/workflows/release.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e2cece..931f3fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,6 @@ name: CI on: push: - branches: - - '**' tags-ignore: - 'v*' pull_request: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c65d45f..62efd07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -164,7 +164,6 @@ jobs: needs: - prepare - csharp-packages - if: secrets.NUGET_API_KEY != '' steps: - name: Setup .NET @@ -179,6 +178,7 @@ jobs: path: dist/nuget - name: Push packages to NuGet.org + if: env.NUGET_API_KEY != '' shell: bash env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} From 2bee08a3f1bbe0ed97a09dc915d41e780e43e2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:37:52 -0400 Subject: [PATCH 03/16] Trigger workflows From b9b48dec563a10348c34993d6ea558ebdcac55b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:38:48 -0400 Subject: [PATCH 04/16] Add manual workflow dispatch --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 931f3fd..408973d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: tags-ignore: - 'v*' pull_request: + workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62efd07..98cc4ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,17 @@ on: push: tags: - 'v*' + workflow_dispatch: + inputs: + version: + description: Package version to validate during manual runs. + required: true + type: string + publish_nuget: + description: Publish packages to NuGet.org during a manual run. + required: false + default: false + type: boolean permissions: contents: write @@ -24,7 +35,11 @@ jobs: id: version shell: bash run: | - VERSION="${GITHUB_REF_NAME#v}" + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + VERSION='${{ inputs.version }}' + else + VERSION="${GITHUB_REF_NAME#v}" + fi if [ -z "$VERSION" ]; then echo "Tag version is empty" >&2 exit 1 @@ -141,6 +156,7 @@ jobs: needs: - rust-artifacts - csharp-packages + if: github.event_name != 'workflow_dispatch' steps: - name: Download all artifacts @@ -164,6 +180,7 @@ jobs: needs: - prepare - csharp-packages + if: github.event_name != 'workflow_dispatch' || inputs.publish_nuget steps: - name: Setup .NET From 03492a60b9eca47f2b61a9c4729fe0b03af220d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:42:48 -0400 Subject: [PATCH 05/16] Probe CI startup failure --- .github/workflows/ci.yml | 92 ++-------------------------------------- 1 file changed, 4 insertions(+), 88 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 408973d..977f536 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,97 +15,13 @@ env: CARGO_TERM_COLOR: always jobs: - rust-lint: - name: Rust Lint + smoke: + name: Smoke runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - - - name: Check formatting - run: cargo fmt --manifest-path rust/Cargo.toml --all --check - - - name: Run clippy - run: cargo clippy -q --manifest-path rust/Cargo.toml --workspace --tests -- -D warnings - - rust-build: - name: Rust Build (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - - - name: Test pinget-core - run: cargo test -p pinget-core --manifest-path rust/Cargo.toml - - - name: Test pinget-cli - run: cargo test -p pinget-cli --manifest-path rust/Cargo.toml - - - name: Build pinget-cli - run: cargo build -p pinget-cli --manifest-path rust/Cargo.toml - - dotnet: - name: Dotnet Build - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 10.0.x - - - name: Install Pester - shell: pwsh - run: | - Set-PSRepository PSGallery -InstallationPolicy Trusted - Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck - - - name: Restore solution - run: dotnet restore dotnet/Devolutions.Pinget.slnx - - - name: Build solution - run: dotnet build dotnet/Devolutions.Pinget.slnx -c Release --no-restore - - - name: Run core tests - run: dotnet test dotnet/src/Devolutions.Pinget.Core.Tests/Devolutions.Pinget.Core.Tests.csproj -c Release --no-build - - - name: Run PowerShell tests - shell: pwsh - run: pwsh -NoLogo -NoProfile -File (Resolve-Path 'dotnet/tests/RunTests.ps1') - - - name: Validate NuGet packing - shell: pwsh - run: | - New-Item -Path artifacts/ci-nuget -ItemType Directory -Force | Out-Null - dotnet pack dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj -c Release --no-build -o artifacts/ci-nuget - dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj -c Release --no-build -o artifacts/ci-nuget - dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj -c Release --no-build -o artifacts/ci-nuget \ No newline at end of file + - name: Smoke test + run: echo "CI smoke test" \ No newline at end of file From 60e570900a3a0141e3ae976bb43b05d577a51259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:44:02 -0400 Subject: [PATCH 06/16] Restore Rust lint CI --- .github/workflows/ci.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 977f536..9024350 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,13 +15,26 @@ env: CARGO_TERM_COLOR: always jobs: - smoke: - name: Smoke + rust-lint: + name: Rust Lint runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Smoke test - run: echo "CI smoke test" \ No newline at end of file + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + + - name: Check formatting + run: cargo fmt --manifest-path rust/Cargo.toml --all --check + + - name: Run clippy + run: cargo clippy -q --manifest-path rust/Cargo.toml --workspace --tests -- -D warnings \ No newline at end of file From 6fa3e055b5a881f8f8205e40a44881f31f79b0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:45:56 -0400 Subject: [PATCH 07/16] Use rustup in CI --- .github/workflows/ci.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9024350..424f7c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,14 +24,11 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust + shell: bash + run: | + rustup toolchain install stable --profile minimal + rustup component add rustfmt clippy --toolchain stable + rustup default stable - name: Check formatting run: cargo fmt --manifest-path rust/Cargo.toml --all --check From 58b826edd53911330a44e0fc9ba2b29d908aebbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:50:19 -0400 Subject: [PATCH 08/16] Restore full CI --- .github/workflows/ci.yml | 79 +++++++++++++++++++++++++++++- rust/crates/pinget-core/src/lib.rs | 12 ++--- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 424f7c8..c4efeb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,4 +34,81 @@ jobs: run: cargo fmt --manifest-path rust/Cargo.toml --all --check - name: Run clippy - run: cargo clippy -q --manifest-path rust/Cargo.toml --workspace --tests -- -D warnings \ No newline at end of file + run: cargo clippy -q --manifest-path rust/Cargo.toml --workspace --tests -- -D warnings + + rust-build: + name: Rust Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust on Linux + if: runner.os != 'Windows' + shell: bash + run: | + rustup toolchain install stable --profile minimal + rustup default stable + + - name: Setup Rust on Windows + if: runner.os == 'Windows' + shell: pwsh + run: | + rustup toolchain install stable --profile minimal + rustup default stable + + - name: Test pinget-core + run: cargo test -p pinget-core --manifest-path rust/Cargo.toml + + - name: Test pinget-cli + run: cargo test -p pinget-cli --manifest-path rust/Cargo.toml + + - name: Build pinget-cli + run: cargo build -p pinget-cli --manifest-path rust/Cargo.toml + + dotnet: + name: Dotnet Build + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Install Pester + shell: pwsh + run: | + Set-PSRepository PSGallery -InstallationPolicy Trusted + Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck + + - name: Restore solution + run: dotnet restore dotnet/Devolutions.Pinget.slnx + + - name: Build solution + run: dotnet build dotnet/Devolutions.Pinget.slnx -c Release --no-restore + + - name: Run core tests + run: dotnet test dotnet/src/Devolutions.Pinget.Core.Tests/Devolutions.Pinget.Core.Tests.csproj -c Release --no-build + + - name: Run PowerShell tests + shell: pwsh + run: pwsh -NoLogo -NoProfile -File (Resolve-Path 'dotnet/tests/RunTests.ps1') + + - name: Validate NuGet packing + shell: pwsh + run: | + New-Item -Path artifacts/ci-nuget -ItemType Directory -Force | Out-Null + dotnet pack dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj -c Release --no-build -o artifacts/ci-nuget + dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj -c Release --no-build -o artifacts/ci-nuget + dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj -c Release --no-build -o artifacts/ci-nuget \ No newline at end of file diff --git a/rust/crates/pinget-core/src/lib.rs b/rust/crates/pinget-core/src/lib.rs index 150f992..10bb561 100644 --- a/rust/crates/pinget-core/src/lib.rs +++ b/rust/crates/pinget-core/src/lib.rs @@ -2022,7 +2022,7 @@ impl Repository { .headers() .get("x-ms-meta-sourceversion") .and_then(|value| value.to_str().ok()) - .map(str::to_string); + .map(str::to_owned); let bytes = response.bytes().context("failed to read preindexed package bytes")?; let payload = bytes.to_vec(); let index_bytes = extract_zip_member(&payload, "Public/index.db") @@ -2055,7 +2055,7 @@ impl Repository { source.identifier = info.source_identifier.clone(); } source.last_update = Some(Utc::now()); - source.source_version = choose_contract(&info.server_supported_versions).map(str::to_string); + source.source_version = choose_contract(&info.server_supported_versions).map(str::to_owned); Ok("refreshed information cache".to_owned()) } @@ -4467,7 +4467,7 @@ fn yaml_scalar_string(value: &YamlValue) -> Option { fn yaml_string_list(root: &YamlMapping, key: &str) -> Vec { root.get(YamlValue::from(key)) .and_then(YamlValue::as_sequence) - .map(|items| items.iter().filter_map(YamlValue::as_str).map(str::to_string).collect()) + .map(|items| items.iter().filter_map(YamlValue::as_str).map(str::to_owned).collect()) .unwrap_or_default() } @@ -4523,14 +4523,14 @@ fn yaml_package_dependencies(root: &YamlMapping) -> Vec { } fn json_string(value: &JsonValue, key: &str) -> Option { - value.get(key).and_then(JsonValue::as_str).map(str::to_string) + value.get(key).and_then(JsonValue::as_str).map(str::to_owned) } fn json_string_list(value: &JsonValue, key: &str) -> Vec { value .get(key) .and_then(JsonValue::as_array) - .map(|items| items.iter().filter_map(JsonValue::as_str).map(str::to_string).collect()) + .map(|items| items.iter().filter_map(JsonValue::as_str).map(str::to_owned).collect()) .unwrap_or_default() } @@ -4595,7 +4595,7 @@ fn json_installer_switches(value: &JsonValue) -> InstallerSwitches { switches .and_then(|switches| switches.get(key)) .and_then(JsonValue::as_str) - .map(str::to_string) + .map(str::to_owned) }; InstallerSwitches { From 9aba66474be8743beac052ae187c36296c5713c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 14:57:56 -0400 Subject: [PATCH 09/16] Fix CI build and PowerShell compatibility --- .../src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj | 2 +- dotnet/src/Devolutions.Pinget.Core/Repository.cs | 2 +- .../Devolutions.Pinget.PowerShell.Cmdlets.csproj | 2 +- .../Devolutions.Pinget.PowerShell.Engine.csproj | 2 +- dotnet/tests/RunTests.ps1 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj b/dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj index 418431a..62a8689 100644 --- a/dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj +++ b/dotnet/src/Devolutions.Pinget.Core/Devolutions.Pinget.Core.csproj @@ -1,7 +1,7 @@ - net10.0 + net8.0;net10.0 enable enable true diff --git a/dotnet/src/Devolutions.Pinget.Core/Repository.cs b/dotnet/src/Devolutions.Pinget.Core/Repository.cs index d4c2ebe..c91199a 100644 --- a/dotnet/src/Devolutions.Pinget.Core/Repository.cs +++ b/dotnet/src/Devolutions.Pinget.Core/Repository.cs @@ -2019,7 +2019,7 @@ private static bool SearchMatchHasUnknownVersion(SearchMatch match) => private static string Sha256Hex(byte[] data) { var hash = SHA256.HashData(data); - return Convert.ToHexStringLower(hash); + return Convert.ToHexString(hash).ToLowerInvariant(); } private class VersionComparer : IComparer diff --git a/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj b/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj index d404b77..8382169 100644 --- a/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj +++ b/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj @@ -8,7 +8,7 @@ - net10.0 + net8.0;net10.0 enable enable true diff --git a/dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj b/dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj index 7c17438..9edeba9 100644 --- a/dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj +++ b/dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj @@ -4,7 +4,7 @@ - net10.0 + net8.0;net10.0 enable enable true diff --git a/dotnet/tests/RunTests.ps1 b/dotnet/tests/RunTests.ps1 index 8dc787d..601dafe 100644 --- a/dotnet/tests/RunTests.ps1 +++ b/dotnet/tests/RunTests.ps1 @@ -1,6 +1,6 @@ [CmdletBinding()] param( - [string]$ModulePath = (Join-Path $PSScriptRoot '..\src\Devolutions.Pinget.PowerShell.Cmdlets\bin\Release\net10.0\Pinget.psd1'), + [string]$ModulePath = (Join-Path $PSScriptRoot '..\src\Devolutions.Pinget.PowerShell.Cmdlets\bin\Release\net8.0\Pinget.psd1'), [string]$SourceArgument = 'https://api.winget.pro/4259fd23-6fcd-46bf-9287-be8833cfbdd5' ) From 5071d62895de5222cd0b8d984e5e80fa9f973248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 15:28:15 -0400 Subject: [PATCH 10/16] Use allowed actions in release workflow --- .github/workflows/release.yml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98cc4ff..6d6d724 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,13 +65,19 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + - name: Setup Rust on Linux + if: runner.os != 'Windows' + shell: bash + run: | + rustup toolchain install stable --profile minimal + rustup default stable - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust + - name: Setup Rust on Windows + if: runner.os == 'Windows' + shell: pwsh + run: | + rustup toolchain install stable --profile minimal + rustup default stable - name: Build release binary run: cargo build -p pinget-cli --manifest-path rust/Cargo.toml --release @@ -166,13 +172,10 @@ jobs: merge-multiple: true - name: Create GitHub release - uses: softprops/action-gh-release@v2 - with: - generate_release_notes: true - files: | - dist/release/*.zip - dist/release/*.nupkg - dist/release/*.snupkg + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "$GITHUB_REF_NAME" dist/release/*.zip dist/release/*.nupkg dist/release/*.snupkg --generate-notes publish-nuget: name: Publish NuGet From fe4316cfdac1a40cc94688cf9088c1ddf98ee6fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 15:42:11 -0400 Subject: [PATCH 11/16] Release from master with dry-run support --- .github/workflows/release.yml | 94 ++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d6d724..1d60276 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,16 +2,21 @@ name: Release on: push: - tags: - - 'v*' + branches: + - master workflow_dispatch: inputs: version: - description: Package version to validate during manual runs. - required: true + description: Package version to release. Defaults to the version detected from source files. + required: false type: string + dry_run: + description: Build and package only; skip tag creation, GitHub release, and NuGet publication. + required: false + default: true + type: boolean publish_nuget: - description: Publish packages to NuGet.org during a manual run. + description: Publish packages to NuGet.org during a manual release. required: false default: false type: boolean @@ -29,22 +34,54 @@ jobs: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.tag_name }} + dry_run: ${{ steps.inputs.outputs.dry_run }} steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve inputs + id: inputs + shell: bash + run: | + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${{ inputs.dry_run }}" = "true" ]; then + echo "dry_run=true" >> "$GITHUB_OUTPUT" + else + echo "dry_run=false" >> "$GITHUB_OUTPUT" + fi + - name: Compute package version id: version shell: bash run: | - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - VERSION='${{ inputs.version }}' + VERSION_INPUT='${{ inputs.version }}' + if [ -n "$VERSION_INPUT" ]; then + VERSION="$VERSION_INPUT" else - VERSION="${GITHUB_REF_NAME#v}" + CORE_VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' rust/crates/pinget-core/Cargo.toml | head -n 1) + CLI_VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' rust/crates/pinget-cli/Cargo.toml | head -n 1) + DOTNET_VERSION=$(sed -n 's/^const string Version = "\(.*\)";$/\1/p' dotnet/src/Devolutions.Pinget.Cli/Program.cs | head -n 1) + if [ -z "$CORE_VERSION" ] || [ -z "$CLI_VERSION" ] || [ -z "$DOTNET_VERSION" ]; then + echo "Unable to detect version from source files" >&2 + exit 1 + fi + if [ "$CORE_VERSION" != "$CLI_VERSION" ] || [ "$CORE_VERSION" != "$DOTNET_VERSION" ]; then + echo "Version mismatch detected: pinget-core=$CORE_VERSION pinget-cli=$CLI_VERSION dotnet=$DOTNET_VERSION" >&2 + exit 1 + fi + VERSION="$CORE_VERSION" fi if [ -z "$VERSION" ]; then - echo "Tag version is empty" >&2 + echo "Package version is empty" >&2 + exit 1 + fi + if [ "${{ steps.inputs.outputs.dry_run }}" != "true" ] && [ "${GITHUB_REF_NAME}" != "master" ]; then + echo "Releases must run from the master branch; use dry_run=true for validation on other refs" >&2 exit 1 fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag_name=v$VERSION" >> "$GITHUB_OUTPUT" rust-artifacts: name: Rust Artifacts (${{ matrix.asset_name }}) @@ -156,13 +193,46 @@ jobs: dist/nuget/*.snupkg if-no-files-found: error + create-tag: + name: Create Tag + runs-on: ubuntu-latest + needs: + - prepare + - rust-artifacts + - csharp-packages + if: needs.prepare.outputs.dry_run != 'true' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Ensure tag does not already exist + shell: bash + run: | + if git ls-remote --exit-code --tags origin "refs/tags/${{ needs.prepare.outputs.tag_name }}" >/dev/null 2>&1; then + echo "Tag ${{ needs.prepare.outputs.tag_name }} already exists on origin" >&2 + exit 1 + fi + + - name: Create and push tag + shell: bash + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "${{ needs.prepare.outputs.tag_name }}" "${GITHUB_SHA}" -m "Release ${{ needs.prepare.outputs.tag_name }}" + git push origin "${{ needs.prepare.outputs.tag_name }}" + github-release: name: GitHub Release runs-on: ubuntu-latest needs: + - prepare + - create-tag - rust-artifacts - csharp-packages - if: github.event_name != 'workflow_dispatch' + if: needs.prepare.outputs.dry_run != 'true' steps: - name: Download all artifacts @@ -175,7 +245,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create "$GITHUB_REF_NAME" dist/release/*.zip dist/release/*.nupkg dist/release/*.snupkg --generate-notes + gh release create "${{ needs.prepare.outputs.tag_name }}" dist/release/*.zip dist/release/*.nupkg dist/release/*.snupkg --generate-notes publish-nuget: name: Publish NuGet @@ -183,7 +253,7 @@ jobs: needs: - prepare - csharp-packages - if: github.event_name != 'workflow_dispatch' || inputs.publish_nuget + if: needs.prepare.outputs.dry_run != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_nuget) steps: - name: Setup .NET From 86f859c8fb73703980f635b404d18f4c194d0544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 16:17:51 -0400 Subject: [PATCH 12/16] Add release preflight and dashed inputs --- .github/workflows/release.yml | 142 ++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d60276..96955ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,12 +10,12 @@ on: description: Package version to release. Defaults to the version detected from source files. required: false type: string - dry_run: + dry-run: description: Build and package only; skip tag creation, GitHub release, and NuGet publication. required: false default: true type: boolean - publish_nuget: + publish-nuget: description: Publish packages to NuGet.org during a manual release. required: false default: false @@ -29,64 +29,84 @@ env: CARGO_TERM_COLOR: always jobs: - prepare: - name: Prepare Version + preflight: + name: Preflight runs-on: ubuntu-latest outputs: - version: ${{ steps.version.outputs.version }} - tag_name: ${{ steps.version.outputs.tag_name }} - dry_run: ${{ steps.inputs.outputs.dry_run }} + version: ${{ steps.info.outputs.version }} + tag-name: ${{ steps.info.outputs.tag-name }} + dry-run: ${{ steps.info.outputs.dry-run }} + publish-nuget: ${{ steps.info.outputs.publish-nuget }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Resolve inputs - id: inputs - shell: bash + - name: Package information + id: info + shell: pwsh run: | - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${{ inputs.dry_run }}" = "true" ]; then - echo "dry_run=true" >> "$GITHUB_OUTPUT" - else - echo "dry_run=false" >> "$GITHUB_OUTPUT" - fi + $IsMasterBranch = ('${{ github.ref_name }}' -eq 'master') + $IsWorkflowDispatch = ('${{ github.event_name }}' -eq 'workflow_dispatch') - - name: Compute package version - id: version - shell: bash - run: | - VERSION_INPUT='${{ inputs.version }}' - if [ -n "$VERSION_INPUT" ]; then - VERSION="$VERSION_INPUT" - else - CORE_VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' rust/crates/pinget-core/Cargo.toml | head -n 1) - CLI_VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' rust/crates/pinget-cli/Cargo.toml | head -n 1) - DOTNET_VERSION=$(sed -n 's/^const string Version = "\(.*\)";$/\1/p' dotnet/src/Devolutions.Pinget.Cli/Program.cs | head -n 1) - if [ -z "$CORE_VERSION" ] || [ -z "$CLI_VERSION" ] || [ -z "$DOTNET_VERSION" ]; then - echo "Unable to detect version from source files" >&2 - exit 1 - fi - if [ "$CORE_VERSION" != "$CLI_VERSION" ] || [ "$CORE_VERSION" != "$DOTNET_VERSION" ]; then - echo "Version mismatch detected: pinget-core=$CORE_VERSION pinget-cli=$CLI_VERSION dotnet=$DOTNET_VERSION" >&2 - exit 1 - fi - VERSION="$CORE_VERSION" - fi - if [ -z "$VERSION" ]; then - echo "Package version is empty" >&2 - exit 1 - fi - if [ "${{ steps.inputs.outputs.dry_run }}" != "true" ] && [ "${GITHUB_REF_NAME}" != "master" ]; then - echo "Releases must run from the master branch; use dry_run=true for validation on other refs" >&2 - exit 1 - fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "tag_name=v$VERSION" >> "$GITHUB_OUTPUT" + $DryRun = $false + $PublishNuget = $true + + if ($IsWorkflowDispatch) { + try { $DryRun = [System.Boolean]::Parse('${{ inputs['dry-run'] }}') } catch { $DryRun = $true } + try { $PublishNuget = [System.Boolean]::Parse('${{ inputs['publish-nuget'] }}') } catch { $PublishNuget = $false } + } + + if (-not $IsMasterBranch) { + $DryRun = $true + $PublishNuget = $false + } + + if ($DryRun) { + $PublishNuget = $false + } + + $Version = '${{ inputs.version }}' + if ([string]::IsNullOrWhiteSpace($Version)) { + $coreVersion = Select-String -Path 'rust/crates/pinget-core/Cargo.toml' -Pattern '^version = "([^"]+)"$' | Select-Object -First 1 + $cliVersion = Select-String -Path 'rust/crates/pinget-cli/Cargo.toml' -Pattern '^version = "([^"]+)"$' | Select-Object -First 1 + $dotnetVersion = Select-String -Path 'dotnet/src/Devolutions.Pinget.Cli/Program.cs' -Pattern '^const string Version = "([^"]+)";$' | Select-Object -First 1 + + if (($null -eq $coreVersion) -or ($null -eq $cliVersion) -or ($null -eq $dotnetVersion)) { + throw 'Unable to detect version from source files' + } + + $coreVersionValue = $coreVersion.Matches[0].Groups[1].Value + $cliVersionValue = $cliVersion.Matches[0].Groups[1].Value + $dotnetVersionValue = $dotnetVersion.Matches[0].Groups[1].Value + + if (($coreVersionValue -ne $cliVersionValue) -or ($coreVersionValue -ne $dotnetVersionValue)) { + throw "Version mismatch detected: pinget-core=$coreVersionValue pinget-cli=$cliVersionValue dotnet=$dotnetVersionValue" + } + + $Version = $coreVersionValue + } + + if ([string]::IsNullOrWhiteSpace($Version)) { + throw 'Package version is empty' + } + + $TagName = "v$Version" + + "version=$Version" >> $Env:GITHUB_OUTPUT + "tag-name=$TagName" >> $Env:GITHUB_OUTPUT + "dry-run=$($DryRun.ToString().ToLower())" >> $Env:GITHUB_OUTPUT + "publish-nuget=$($PublishNuget.ToString().ToLower())" >> $Env:GITHUB_OUTPUT + + Write-Host "::notice::Version: $Version" + Write-Host "::notice::Tag: $TagName" + Write-Host "::notice::DryRun: $DryRun" + Write-Host "::notice::PublishNuget: $PublishNuget" rust-artifacts: name: Rust Artifacts (${{ matrix.asset_name }}) runs-on: ${{ matrix.os }} - needs: prepare + needs: preflight strategy: fail-fast: false matrix: @@ -143,7 +163,7 @@ jobs: csharp-packages: name: C# Packages runs-on: windows-latest - needs: prepare + needs: preflight steps: - name: Checkout @@ -176,7 +196,7 @@ jobs: - name: Pack NuGet packages shell: pwsh env: - PACKAGE_VERSION: ${{ needs.prepare.outputs.version }} + PACKAGE_VERSION: ${{ needs.preflight.outputs.version }} run: | New-Item -Path dist/nuget -ItemType Directory -Force | Out-Null $packArgs = @('-c', 'Release', '--no-build', '-p:ContinuousIntegrationBuild=true', "-p:Version=$env:PACKAGE_VERSION", '-p:IncludeSymbols=true', '-p:SymbolPackageFormat=snupkg', '-o', 'dist/nuget') @@ -197,10 +217,10 @@ jobs: name: Create Tag runs-on: ubuntu-latest needs: - - prepare + - preflight - rust-artifacts - csharp-packages - if: needs.prepare.outputs.dry_run != 'true' + if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }} steps: - name: Checkout @@ -211,8 +231,8 @@ jobs: - name: Ensure tag does not already exist shell: bash run: | - if git ls-remote --exit-code --tags origin "refs/tags/${{ needs.prepare.outputs.tag_name }}" >/dev/null 2>&1; then - echo "Tag ${{ needs.prepare.outputs.tag_name }} already exists on origin" >&2 + if git ls-remote --exit-code --tags origin "refs/tags/${{ needs.preflight.outputs.tag-name }}" >/dev/null 2>&1; then + echo "Tag ${{ needs.preflight.outputs.tag-name }} already exists on origin" >&2 exit 1 fi @@ -221,18 +241,18 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git tag -a "${{ needs.prepare.outputs.tag_name }}" "${GITHUB_SHA}" -m "Release ${{ needs.prepare.outputs.tag_name }}" - git push origin "${{ needs.prepare.outputs.tag_name }}" + git tag -a "${{ needs.preflight.outputs.tag-name }}" "${GITHUB_SHA}" -m "Release ${{ needs.preflight.outputs.tag-name }}" + git push origin "${{ needs.preflight.outputs.tag-name }}" github-release: name: GitHub Release runs-on: ubuntu-latest needs: - - prepare + - preflight - create-tag - rust-artifacts - csharp-packages - if: needs.prepare.outputs.dry_run != 'true' + if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }} steps: - name: Download all artifacts @@ -245,15 +265,17 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create "${{ needs.prepare.outputs.tag_name }}" dist/release/*.zip dist/release/*.nupkg dist/release/*.snupkg --generate-notes + gh release create "${{ needs.preflight.outputs.tag-name }}" dist/release/*.zip dist/release/*.nupkg dist/release/*.snupkg --generate-notes publish-nuget: name: Publish NuGet runs-on: ubuntu-latest needs: - - prepare + - preflight - csharp-packages - if: needs.prepare.outputs.dry_run != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_nuget) + - create-tag + - github-release + if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false && fromJSON(needs.preflight.outputs.publish-nuget) == true }} steps: - name: Setup .NET From 5ba7d1717bd28dcabf534d8ad709f3321ab9ada8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 18:04:31 -0400 Subject: [PATCH 13/16] Expand Rust release matrix and update action versions --- .github/workflows/ci.yml | 8 +-- .github/workflows/release.yml | 132 ++++++++++++++++++++++++---------- 2 files changed, 98 insertions(+), 42 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4efeb1..2c93b88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Rust shell: bash @@ -48,7 +48,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Rust on Linux if: runner.os != 'Windows' @@ -79,10 +79,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96955ae..3ef7ddd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Package information id: info @@ -104,60 +104,116 @@ jobs: Write-Host "::notice::PublishNuget: $PublishNuget" rust-artifacts: - name: Rust Artifacts (${{ matrix.asset_name }}) + name: Rust Artifacts (${{ matrix.name }}) runs-on: ${{ matrix.os }} needs: preflight strategy: fail-fast: false matrix: include: - - os: windows-latest - asset_name: pinget-windows-x64.zip - binary_path: rust/target/release/pinget.exe - - os: ubuntu-latest - asset_name: pinget-linux-x64.zip - binary_path: rust/target/release/pinget + - name: linux-x64 + os: ubuntu-latest + target: x86_64-unknown-linux-gnu + archive_name: pinget-linux-x64.zip + binary_name: pinget + - name: linux-arm64 + os: ubuntu-latest + target: aarch64-unknown-linux-gnu + archive_name: pinget-linux-arm64.zip + binary_name: pinget + - name: windows-x64 + os: windows-latest + target: x86_64-pc-windows-msvc + archive_name: pinget-windows-x64.zip + binary_name: pinget.exe + - name: windows-arm64 + os: windows-latest + target: aarch64-pc-windows-msvc + archive_name: pinget-windows-arm64.zip + binary_name: pinget.exe + - name: macos-x64 + os: macos-14 + target: x86_64-apple-darwin + archive_name: pinget-macos-x64.zip + binary_name: pinget + - name: macos-arm64 + os: macos-14 + target: aarch64-apple-darwin + archive_name: pinget-macos-arm64.zip + binary_name: pinget steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Setup Rust on Linux - if: runner.os != 'Windows' - shell: bash + - name: Install Rust toolchain + shell: pwsh run: | - rustup toolchain install stable --profile minimal + rustup toolchain install stable --profile minimal --target "${{ matrix.target }}" rustup default stable - - name: Setup Rust on Windows - if: runner.os == 'Windows' + - name: Install Linux ARM64 linker + if: matrix.target == 'aarch64-unknown-linux-gnu' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Configure static MSVC runtime + if: contains(matrix.target, 'windows-msvc') shell: pwsh run: | - rustup toolchain install stable --profile minimal - rustup default stable + "RUSTFLAGS=-C target-feature=+crt-static" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Build release binary - run: cargo build -p pinget-cli --manifest-path rust/Cargo.toml --release - - - name: Archive Windows binary - if: runner.os == 'Windows' shell: pwsh + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc run: | - New-Item -Path dist -ItemType Directory -Force | Out-Null - Compress-Archive -LiteralPath '${{ matrix.binary_path }}' -DestinationPath "dist/${{ matrix.asset_name }}" -Force + cargo build --locked --release --package pinget-cli --bin pinget --manifest-path rust/Cargo.toml --target "${{ matrix.target }}" - - name: Archive Unix binary - if: runner.os != 'Windows' - shell: bash + - name: Package artifacts + shell: pwsh + env: + TARGET: ${{ matrix.target }} + PINGET_BIN_NAME: ${{ matrix.binary_name }} + PINGET_ARCHIVE_NAME: ${{ matrix.archive_name }} run: | - mkdir -p dist - zip -j "dist/${{ matrix.asset_name }}" "${{ matrix.binary_path }}" + Add-Type -AssemblyName System.IO.Compression.FileSystem + + $target = $env:TARGET + $binaryName = $env:PINGET_BIN_NAME + $archiveName = $env:PINGET_ARCHIVE_NAME + $binaryPath = [System.IO.Path]::Combine('rust', 'target', $target, 'release', $binaryName) + + if (-not (Test-Path -Path $binaryPath)) { + throw "Binary not found: $binaryPath" + } + + if (Test-Path -Path $archiveName) { + Remove-Item -Path $archiveName -Force + } + + $archive = [System.IO.Compression.ZipFile]::Open($archiveName, [System.IO.Compression.ZipArchiveMode]::Create) + try { + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile( + $archive, + $binaryPath, + $binaryName, + [System.IO.Compression.CompressionLevel]::Optimal + ) | Out-Null + } + finally { + $archive.Dispose() + } + + Write-Host "Created $archiveName" - name: Upload Rust artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: - name: ${{ matrix.asset_name }} - path: dist/${{ matrix.asset_name }} + name: ${{ matrix.archive_name }} + path: ${{ matrix.archive_name }} if-no-files-found: error csharp-packages: @@ -167,10 +223,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x @@ -205,7 +261,7 @@ jobs: dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj @packArgs - name: Upload package artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: csharp-nuget path: | @@ -224,7 +280,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -256,7 +312,7 @@ jobs: steps: - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: dist/release merge-multiple: true @@ -279,12 +335,12 @@ jobs: steps: - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x - name: Download package artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: csharp-nuget path: dist/nuget From d86cbe307bf5dd0bac9cacd8cd7a9be8a40eece5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 18:07:59 -0400 Subject: [PATCH 14/16] Fix Rust release matrix build lockfile usage --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ef7ddd..847a0a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -170,7 +170,7 @@ jobs: env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc run: | - cargo build --locked --release --package pinget-cli --bin pinget --manifest-path rust/Cargo.toml --target "${{ matrix.target }}" + cargo build --release --package pinget-cli --bin pinget --manifest-path rust/Cargo.toml --target "${{ matrix.target }}" - name: Package artifacts shell: pwsh From c538e2e406ba4c485d99bc91cbbcc38f689001b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 18:16:01 -0400 Subject: [PATCH 15/16] Use rustls for pinget-cli HTTP client --- rust/crates/pinget-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/crates/pinget-cli/Cargo.toml b/rust/crates/pinget-cli/Cargo.toml index 24063c6..e23a013 100644 --- a/rust/crates/pinget-cli/Cargo.toml +++ b/rust/crates/pinget-cli/Cargo.toml @@ -17,7 +17,7 @@ pinget-core = { version = "0.1.0", path = "../pinget-core" } chrono = "0.4.44" dirs = "6.0" jsonschema = "0.30" -reqwest = { version = "0.12", features = ["blocking"] } +reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] } rusqlite = { version = "0.39", features = ["bundled"] } serde = { version = "1", features = ["derive"] } serde_json = "1.0.149" From f726281a646369abc08f3df792ca06cd8cabcaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 24 Apr 2026 19:58:27 -0400 Subject: [PATCH 16/16] Add PowerShell module release packaging --- .github/workflows/release.yml | 39 +++++++++++++++++++ ...volutions.Pinget.PowerShell.Cmdlets.csproj | 9 +++++ 2 files changed, 48 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 847a0a5..6d8308f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -260,6 +260,38 @@ jobs: dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Engine/Devolutions.Pinget.PowerShell.Engine.csproj @packArgs dotnet pack dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj @packArgs + - name: Package PowerShell module + shell: pwsh + env: + PACKAGE_VERSION: ${{ needs.preflight.outputs.version }} + run: | + Add-Type -AssemblyName System.IO.Compression.FileSystem + + $moduleName = 'Pinget' + $moduleVersion = $env:PACKAGE_VERSION + $moduleOutput = Join-Path 'dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/bin/Release' 'net8.0' + $stagingRoot = Join-Path 'dist' 'powershell-module' + $moduleRoot = Join-Path $stagingRoot $moduleName + $archivePath = Join-Path 'dist' ("pinget-powershell-module-$moduleVersion.zip") + + if (-not (Test-Path -Path (Join-Path $moduleOutput 'Pinget.psd1'))) { + throw "PowerShell module manifest not found in $moduleOutput" + } + + if (Test-Path -Path $stagingRoot) { + Remove-Item -Path $stagingRoot -Recurse -Force + } + + if (Test-Path -Path $archivePath) { + Remove-Item -Path $archivePath -Force + } + + New-Item -Path $moduleRoot -ItemType Directory -Force | Out-Null + Copy-Item -Path (Join-Path $moduleOutput '*') -Destination $moduleRoot -Recurse -Force + [System.IO.Compression.ZipFile]::CreateFromDirectory($stagingRoot, $archivePath, [System.IO.Compression.CompressionLevel]::Optimal, $false) + + Write-Host "Created $archivePath" + - name: Upload package artifacts uses: actions/upload-artifact@v7 with: @@ -269,6 +301,13 @@ jobs: dist/nuget/*.snupkg if-no-files-found: error + - name: Upload PowerShell module artifact + uses: actions/upload-artifact@v7 + with: + name: powershell-module + path: dist/pinget-powershell-module-*.zip + if-no-files-found: error + create-tag: name: Create Tag runs-on: ubuntu-latest diff --git a/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj b/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj index 8382169..cb24ffd 100644 --- a/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj +++ b/dotnet/src/Devolutions.Pinget.PowerShell.Cmdlets/Devolutions.Pinget.PowerShell.Cmdlets.csproj @@ -13,12 +13,21 @@ enable true true + $(TargetsForTfmSpecificContentInPackage);AddPowerShellModuleFilesToPackage Devolutions.Pinget.PowerShell.Cmdlets Devolutions.Pinget.PowerShell.Cmdlets Devolutions.Pinget.PowerShell.Cmdlets PowerShell cmdlets for Pinget. + + + + lib\$(TargetFramework)\%(RecursiveDir)%(Filename)%(Extension) + + + +