Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Build
run-name: >-
${{ github.event_name == 'pull_request' && format('Checking that #{0} builds', github.event.pull_request.number)
|| format('Building {0}', github.ref_name) }}

# Trunk-based: main is the only long-lived branch. Every pull request and every push to main builds the
# whole solution (the library and all Examples) and packs the library, but nothing is published here:
# this is a public NuGet package, and NuGet versions are permanent, so a release is a deliberate act -
# publishing a GitHub Release v{semver}, which runs publish.yml. There is no test suite, so a clean build
# and pack is the check that gates the merge (the required status check is this job's name, `build`).
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: build
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency:
# A newer push to the same pull request cancels the older build; builds of main never cancel each other.
group: build-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.run_id }}
cancel-in-progress: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json

# The csproj turns high and critical NuGet advisories (NU1903/NU1904), direct or transitive, into
# errors, so a vulnerable dependency fails this step rather than reaching a release.
- name: Restore
run: dotnet restore ConvertApi.sln

- name: Build the library and the examples
run: dotnet build ConvertApi.sln -c Release --no-restore -p:ContinuousIntegrationBuild=true

# A pre-release version that sorts below any real release and names the run, so a package
# downloaded from this run can be tried out but never mistaken for a published one.
- name: Pack
id: pack
env:
RUN: ${{ github.run_number }}
SHORT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
VERSION="0.0.0-ci.$RUN.g${SHORT_SHA:0:7}"
dotnet pack ConvertApi/ConvertApi.csproj -c Release --no-build -o out \
-p:Version="$VERSION" -p:ContinuousIntegrationBuild=true
test -f "out/ConvertApi.$VERSION.nupkg"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ConvertApi-${{ steps.pack.outputs.version }}
path: out/
retention-days: 14
if-no-files-found: error

- name: Summary
env:
VERSION: ${{ steps.pack.outputs.version }}
run: |
{
echo "| | |"
echo "|---|---|"
echo "| Package | \`ConvertApi $VERSION\` (artifact only, not published) |"
echo "| Dependencies | $(unzip -p "out/ConvertApi.$VERSION.nupkg" ConvertApi.nuspec | grep -o 'dependency id="[^"]*" version="[^"]*"' | sort -u | sed 's/dependency id="\([^"]*\)" version="\([^"]*\)"/\1 \2/' | paste -sd ',' - | sed 's/,/, /g') |"
} >> "$GITHUB_STEP_SUMMARY"
209 changes: 209 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
name: Publish
run-name: Publishing ConvertApi ${{ github.event.release.tag_name }} to nuget.org

# A release is a GitHub Release, never a merge. Merging to main only builds (build.yml); publishing the
# Release v{semver} runs this file, which packs that tag's commit with the tag's version and pushes it to
# nuget.org. NuGet versions are public and permanent - a pushed version can be unlisted but never
# replaced - so nothing here retries, overwrites or skips a duplicate.
#
# gh release create v3.1.19 --target main --generate-notes -> ConvertApi 3.1.19
# gh release create v3.2.0-preview.1 --target main --prerelease ... -> ConvertApi 3.2.0-preview.1
#
# A stable version must be a commit on main. A pre-release may be any commit, so a branch can be tried
# out as a preview package before it is merged.
on:
release:
types: [published]

permissions:
contents: read

env:
# Only this account may publish a Release that reaches nuget.org. GitHub cannot express this as a
# permission on the Team plan, so it is checked here; the real controls are the v* tag ruleset (only
# repository admins create v* tags) and the nuget environment, which accepts only v* tags.
PUBLISHER: tomasr78
PACKAGE_ID: ConvertApi

jobs:
publish:
name: publish
runs-on: ubuntu-latest
timeout-minutes: 30
environment:
name: nuget
url: https://www.nuget.org/packages/ConvertApi/${{ steps.tag.outputs.version }}
concurrency:
group: publish-nuget
cancel-in-progress: false
permissions:
contents: read
# Only for nuget.org Trusted Publishing (NuGet/login below), which trades this run's OIDC token for
# a short-lived API key. Unused while the repository still publishes with the NUGET_API_KEY secret.
id-token: write
steps:
- name: Check who published the release
run: |
# A re-run keeps GITHUB_ACTOR as the original user; GITHUB_TRIGGERING_ACTOR is whoever pressed
# "Re-run", so both must be the publisher or anyone with write access could replay a release.
if [ "$GITHUB_ACTOR" != "$PUBLISHER" ] || [ "${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}" != "$PUBLISHER" ]; then
echo "::error::Only $PUBLISHER may publish to nuget.org - this release was published by $GITHUB_ACTOR and run by ${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}."
exit 1
fi
echo "Published by $GITHUB_ACTOR"

# The release event runs this file from the tag, so the workflow and the code come from the same commit.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Check the tag and the version
id: tag
env:
TAG: ${{ github.event.release.tag_name }}
PRERELEASE: ${{ github.event.release.prerelease }}
run: |
set -euo pipefail
# SemVer 2.0 without build metadata (nuget.org ignores "+..." and it would only confuse).
SEMVER='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
if ! [[ "$TAG" =~ $SEMVER ]]; then
echo "::error::Tag '$TAG' is not v{major}.{minor}.{patch} or v{major}.{minor}.{patch}-{pre-release}, e.g. v3.1.19 or v3.2.0-preview.1"
exit 1
fi
if [ "$GITHUB_REF" != "refs/tags/$TAG" ]; then
echo "::error::The run is on $GITHUB_REF, not on the release tag refs/tags/$TAG"
exit 1
fi
VERSION="${TAG#v}"
SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
if [ "$(git rev-parse HEAD)" != "$SHA" ]; then
echo "::error::Checked out $(git rev-parse HEAD), but $TAG points at $SHA"
exit 1
fi

# The GitHub "pre-release" tick and the version must agree, so a preview cannot become the
# package's latest stable version by a forgotten tick, and a stable version cannot hide as a pre-release.
if [[ "$VERSION" == *-* ]]; then
if [ "$PRERELEASE" != true ]; then
echo "::error::$TAG is a pre-release version, so the GitHub Release must be marked as a pre-release (gh release create --prerelease)"
exit 1
fi
else
if [ "$PRERELEASE" = true ]; then
echo "::error::$TAG is a stable version, but the GitHub Release is marked as a pre-release. Use a pre-release version such as ${TAG}-preview.1, or untick pre-release."
exit 1
fi
if ! git merge-base --is-ancestor "$SHA" origin/main; then
echo "::error::$TAG ($SHA) is not on main. Stable versions are released from main only; release a branch as a pre-release instead."
exit 1
fi
fi

# nuget.org lists every version ever pushed, unlisted ones included, in lower case.
INDEX=$(curl -fsS --retry 3 "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json")
if jq -e --arg v "${VERSION,,}" '.versions | index($v)' <<<"$INDEX" >/dev/null; then
echo "::error::$PACKAGE_ID $VERSION already exists on nuget.org and can never be pushed again. Release the next version instead."
exit 1
fi
if [[ "$VERSION" != *-* ]]; then
LATEST=$(jq -r '[.versions[] | select(contains("-") | not)] | last // empty' <<<"$INDEX")
if [ -n "$LATEST" ] && [ "$(printf '%s\n%s\n' "$LATEST" "$VERSION" | sort -V | tail -n1)" != "$VERSION" ]; then
echo "::warning::$VERSION is lower than the latest stable $LATEST, so it will not become the version that new installs get."
fi
fi

{
echo "version=$VERSION"
echo "sha=$SHA"
} >> "$GITHUB_OUTPUT"
echo "Publishing $PACKAGE_ID $VERSION from $SHA"

- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json

# The csproj fails the restore on any high or critical advisory in the dependency graph, so a
# vulnerable dependency cannot be published.
- name: Restore
run: dotnet restore ConvertApi/ConvertApi.csproj

- name: Build and pack
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
set -euo pipefail
dotnet build ConvertApi/ConvertApi.csproj -c Release --no-restore \
-p:Version="$VERSION" -p:ContinuousIntegrationBuild=true
dotnet pack ConvertApi/ConvertApi.csproj -c Release --no-build -o out \
-p:Version="$VERSION" -p:ContinuousIntegrationBuild=true
ls -l out

- name: Check the package
env:
VERSION: ${{ steps.tag.outputs.version }}
SHA: ${{ steps.tag.outputs.sha }}
run: |
set -euo pipefail
PKG="out/$PACKAGE_ID.$VERSION.nupkg"
test -f "$PKG" && test -f "out/$PACKAGE_ID.$VERSION.snupkg"
NUSPEC=$(unzip -p "$PKG" "$PACKAGE_ID.nuspec")
grep -q "<version>$VERSION</version>" <<<"$NUSPEC" || { echo "::error::$PKG does not carry version $VERSION"; exit 1; }
grep -q "commit=\"$SHA\"" <<<"$NUSPEC" || { echo "::error::$PKG does not record commit $SHA"; exit 1; }
echo "$NUSPEC" | grep -E '<(id|version|repository|dependency) '

# Trusted Publishing: once a nuget.org policy for this repository, publish.yml and the nuget
# environment exists, setting the repository variable NUGET_USER (the nuget.org profile name that
# owns the policy) switches publishing to a short-lived key, and NUGET_API_KEY can be deleted.
- name: Log in to nuget.org (Trusted Publishing)
id: login
if: vars.NUGET_USER != ''
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
with:
user: ${{ vars.NUGET_USER }}

# No --skip-duplicate: a version that already exists must fail loudly, never pass as published.
# The .snupkg next to the .nupkg is pushed to the symbol server by the same command.
- name: Push to nuget.org
env:
VERSION: ${{ steps.tag.outputs.version }}
NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY || secrets.NUGET_API_KEY }}
run: |
set -euo pipefail
if [ -z "$NUGET_API_KEY" ]; then
echo "::error::No nuget.org key: set up Trusted Publishing (NUGET_USER) or the NUGET_API_KEY secret"
exit 1
fi
dotnet nuget push "out/$PACKAGE_ID.$VERSION.nupkg" \
--api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json

# nuget.org validates and indexes a push before anyone can restore it, usually within minutes. A slow
# index is not a failed release, so this only warns.
- name: Wait for nuget.org to serve the package
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
set -uo pipefail
for _ in $(seq 1 40); do
if curl -fsS "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID,,}/index.json" \
| jq -e --arg v "${VERSION,,}" '.versions | index($v)' >/dev/null; then
echo "$PACKAGE_ID $VERSION is available on nuget.org"
exit 0
fi
sleep 30
done
echo "::warning::$PACKAGE_ID $VERSION was pushed but nuget.org has not served it after 20 minutes. Check https://www.nuget.org/packages/$PACKAGE_ID/$VERSION"

- name: Summary
env:
VERSION: ${{ steps.tag.outputs.version }}
SHA: ${{ steps.tag.outputs.sha }}
AUTH: ${{ steps.login.outcome == 'success' && 'Trusted Publishing' || 'NUGET_API_KEY secret' }}
run: |
{
echo "| | |"
echo "|---|---|"
echo "| Package | [\`$PACKAGE_ID $VERSION\`](https://www.nuget.org/packages/$PACKAGE_ID/$VERSION) |"
echo "| Commit | \`$SHA\` |"
echo "| Published with | $AUTH |"
} >> "$GITHUB_STEP_SUMMARY"
24 changes: 0 additions & 24 deletions .github/workflows/release.yml

This file was deleted.

14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

## [3.1.19] — 2026-10-16

### Security
- Bump `Microsoft.OpenApi.Readers` 1.6.23 → 1.6.31, fixing the high-severity YAML alias-expansion
denial of service [GHSA-7pxr-59rr-hqj2](https://github.com/advisories/GHSA-7pxr-59rr-hqj2)
(patched in 1.6.30). No API change: the converter catalog returns the same converters and tags.

### Changed
- Releases are now cut from a GitHub Release `v{version}`; the package version comes from the tag.
The build now fails on any high or critical advisory in the dependency graph.
- Package metadata: repository URL points to `convertapi-library-dotnet`, the description no longer
names specific .NET versions, release notes link to GitHub Releases, and a symbol package (`.snupkg`)
is published alongside.

## [3.1.15-dev] — 2026-05-14

### Added
Expand Down
27 changes: 16 additions & 11 deletions ConvertApi/ConvertApi.csproj
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>3.1.18</Version>
<!-- The released version comes from the GitHub Release tag: the publish workflow passes -p:Version=3.1.19 for v3.1.19.
This placeholder is what local and pull request builds get, so they can never be mistaken for a release. -->
<Version>0.0.0-local</Version>
<Company>ConvertApi</Company>
<Authors>ConvertApi</Authors>
<PackageProjectUrl>https://www.convertapi.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/ConvertAPI/convertapi-dotnet</RepositoryUrl>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/ConvertAPI/convertapi-library-dotnet</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>file-converter document-converter office-to-pdf office2pdf docx-to-pdf docx2pdf xlsx-to-pdf xlsx2pdf pptx-to-pdf ppt2pdf pdf-merge pdf-split pdf-compress pdf-watermark prdf-extract pdf2extract pdf-protect pdfa ocr redact html-to-pdf web-to-pdf image-to-pdf jpg-to-pdf png-to-pdf email-to-pdf eml-to-pdf msg-to-pdf convertapi pdf docx xlsx pptx office converter csharp dotnet sdk </PackageTags>
<Copyright>2012-2025 ConvertApi</Copyright>
<Copyright>2012-2026 ConvertApi</Copyright>
<PackageLicenseUrl>https://www.convertapi.com/terms</PackageLicenseUrl>
<PackageIconUrl>https://www.convertapi.com/favicon/android-chrome-192x192.png</PackageIconUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageId>ConvertApi</PackageId>
<Product>ConvertApi</Product>
<AssemblyName>ConvertApi</AssemblyName>
<RootNamespace>ConvertApiDotNet</RootNamespace>
<AssemblyVersion>3.1.18.0</AssemblyVersion>
<FileVersion>3.1.18.0</FileVersion>
<PackageReleaseNotes>Development snapshot (-dev prerelease). Changelog will evolve during the development cycle.</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/ConvertAPI/convertapi-library-dotnet/releases</PackageReleaseNotes>
<TargetFrameworks>netstandard2.0;netcoreapp3.1</TargetFrameworks>
<PackageVersion>3.1.18</PackageVersion>
<Description>ConvertAPI for .NET — one call to turn Office files into PDF or back again (DOCX ⇄ PDF, XLSX ⇄ PDF, PPTX ⇄ PDF). Convert HTML, images, ebooks, email, zip; merge, split, watermark, compress, OCR, redact, protect or repair PDF. Async, cross-platform, .NET Standard 2.0+ / .NET Core 5-9</Description>
<Description>ConvertAPI for .NET — one call to turn Office files into PDF or back again (DOCX ⇄ PDF, XLSX ⇄ PDF, PPTX ⇄ PDF). Convert HTML, images, ebooks, email, zip; merge, split, watermark, compress, OCR, redact, protect or repair PDF. Async and cross-platform: targets .NET Standard 2.0, so it runs on .NET Framework 4.6.2+ and every modern .NET.</Description>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!-- A library passes its dependencies on to every consumer, so a known high or critical advisory in any of them,
direct or transitive, fails the build instead of shipping. Moderate and low advisories stay warnings. -->
<NuGetAudit>true</NuGetAudit>
<NuGetAuditMode>all</NuGetAuditMode>
<WarningsAsErrors>$(WarningsAsErrors);NU1903;NU1904</WarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.23" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.31" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions ConvertApi/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ int conversionsConsumed = user.ConversionsConsumed;

### More examples

You can find more advanced examples in the [examples](https://github.com/ConvertAPI/convertapi-dotnet/tree/master/Examples) folder.
You can find more advanced examples in the [examples](https://github.com/ConvertAPI/convertapi-library-dotnet/tree/main/Examples) folder.

#### Converting your first file, complete example:

Expand Down Expand Up @@ -492,4 +492,4 @@ catch (ConvertApiException e)
Please leave all comments, bugs, requests, and issues on the Issues page. We'll respond to your request ASAP!

### License
The ConvertAPI .NET Library is licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php "Read more about the MIT license form") license. Refere to the [LICENSE](https://github.com/ConvertAPI/convertapi-dotnet/blob/master/LICENSE) file for more information.
The ConvertAPI .NET Library is licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php "Read more about the MIT license form") license. Refere to the [LICENSE](https://github.com/ConvertAPI/convertapi-library-dotnet/blob/main/LICENSE) file for more information.
Loading
Loading