Skip to content

ci: run the examples and the README C# code against the API - #93

Merged
tomasr78 merged 4 commits into
mainfrom
ci-examples-staging
Nov 11, 2026
Merged

tomasr78 merged 4 commits into
mainfrom
ci-examples-staging

Conversation

@tomasr78

@tomasr78 tomasr78 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

What this adds

A new workflow, .github/workflows/examples.yml ("Examples"), runs the C# examples and the C# code in README.md against the ConvertAPI API, at https://v2.convertapi.com, the address the SDK uses by default. It uses a token kept for CI, the CONVERTAPI_CI_TOKEN secret, and runs on ubuntu-latest and macos-latest:

  • for pull requests to main and pushes to main that change ConvertApi/, Examples/, README.md, global.json or the CI files, so unrelated changes cost no conversions;
  • every Monday at 05:17 UTC, because the API can break an example without a commit here, as the removed PDF-thumbnail parameter and the blocked URL-to-PDF page did;
  • on demand.

Pull requests from forks are skipped, because they get no secrets. Their changes run on main after the merge.

Each job:

  1. builds the solution and runs all 21 examples in Examples/. Each example runs from an empty folder with stdin closed, so its Console.ReadLine() returns at once;
  2. makes a console project of each of the 44 csharp blocks in README.md and builds them all;
  3. runs the quick start and the 10 blocks under the eight "... in C#" task headings. The other 33 blocks are only built.

One job converts about 40 files, so a workflow run uses about 80 conversions. build.yml is unchanged: it still compiles the examples, and build stays the only required check.

What fails the job

  • An example exits with an error, or prints an API error it caught. The examples catch ConvertApiException, print Status Code: and Response:, and exit with 0. ExceptionHandling is the one exception: it must print Status Code: BadRequest, the error it is written to provoke.
  • A README block does not build. Each compiler error is annotated on the README.md line it comes from.
  • The quick start or a task block exits with an error or saves no file. The text-extraction block must print text instead.
  • Any output contains the API token, or a Secret or ApiKey field, because Actions logs of a public repository are public. A leak fails at once, without a retry, and its line is not printed.
  • A program was built without the CI host below, so nothing deleted its files from the server.

A failed run is tried again up to twice, 30 s apart, for a moment when the API answers 503. Retries show as warnings, so a flaky run stays visible. Program output is printed with workflow commands switched off. Each job writes a table of every example and README block to the run's summary page.

The CI host, and the files left on the server

The programs run exactly as people copy them, against the same API address. The job sets the environment variable CustomAfterMicrosoftCommonTargets to .github/ci/ci-host.targets. MSBuild reads environment variables as properties, and Microsoft.Common.targets imports the file this property names, so every project the job builds imports it. Nothing in the repository imports it: normal builds, and every folder people copy, are unchanged.

The targets file adds .github/ci/CiHost.cs to the programs only (OutputType Exe, not test projects), never to the library or the unit tests. CiHost.cs is a module initializer, so it runs before Main. It:

  • prints the API address in a [ci] line, which the checks require;
  • gives the SDK an HTTP client with the same settings as its default one: an HttpClient with ConvertApiConstants.HttpClientTimeOut, made on first use. Its handler notes the URL of every file the API returns for an upload or a conversion. When the program exits, the host deletes those files from the server, as DeleteFilesAsync does, instead of leaving them for up to 3 hours. A run that leaves files gets a warning, not a failure. The client goes in through the SDK's public new ConvertApi(token, client) constructor, with the current token (none yet), so each program still sets its own token;
  • if a program crashes on a ConvertApiException, prints the API's response, which the crash message leaves out, for example which parameter value the API rejected.

Why this and not the alternatives:

  • A Directory.Build.props or .targets in Examples/ would sit in the folder people copy, and would not reach the README projects, which are made outside the repository.
  • Adding DeleteFilesAsync calls or other CI code to the examples and README blocks would change the code people copy.

It is the .NET counterpart of the Node SDK's node --require ./.github/examples-base-uri.js preload (https://github.com/ConvertAPI/convertapi-library-node/blob/main/.github/workflows/build.yml).

README.md blocks

.github/ci/check_csharp.py readme-build extracts every csharp block. Each complete program (one with using ConvertApiDotNet;) becomes Program.cs of a project made by dotnet new console. The project gets a ProjectReference to ConvertApi/ConvertApi.csproj instead of the NuGet package, so README.md is checked against what the next release ships, not against the version on nuget.org.

The three fragments are spliced into the quick start program and built:

README section Fragment How it is built
Quick start the URL variant, var result = ... new Uri(...) replaces the quick start's var result statement
Set the API token var convertApi = new ConvertApi(...) replaces the quick start's var convertApi statement
Set the conversion location ConvertApi.ApiBaseUri = "https://eu-v2.convertapi.com"; goes before the quick start's first statement; built, never run

No fragment is skipped today. A later fragment that is neither a quick start statement nor a ConvertApi setting is listed as skipped in the job log and on the summary page, with a notice on its README line.

The runs copy sample files from Examples/TestFiles in under the names the blocks use: document.docx (test.docx), document.pdf (test.pdf), page.html (test.html, new) and spreadsheet.xlsx (test.xlsx, new). The two new samples are small hand-written files: an HTML page with a table, and a one-sheet workbook with the same table.

Results so far

  • The same checks ran locally on macOS against the staging API on 2026-11-11, before the switch to production:
    • 21 of 21 examples passed;
    • 44 of 44 README blocks built (41 programs and 3 spliced fragments, none skipped, no warnings), and 11 of 11 runs passed;
    • with temporary edits that are not in this pull request, the job failed as it should: on a compile error in a README block (annotated on its line), on an invalid parameter value (after three attempts, with the API's 400 response), on an example that printed a Secret= line (at once, with the line hidden), and on ExceptionHandling no longer getting its 400.
  • After the switch, checked locally without converting anything:
    • all 44 README blocks build;
    • the CI host is in all 21 examples and in no library or test assembly;
    • a program run without a token prints the [ci] lines and the SDK's own missing-token error;
    • the API accepts the new CI token.
  • The first CI run built everything on both runners and stopped only at the missing secret. The production runs follow once the secret exists.

Before this can go green

The job needs the CONVERTAPI_CI_TOKEN secret twice: as an Actions secret, and as a Dependabot secret, because Dependabot runs read only Dependabot secrets. Until then, each job builds everything and then stops at "Check the API token" with a clear message. pull_request_target is not used.

That step also masks the account's name and email in the log. check_csharp.py hides the account fields that RetrieveUserInformation prints: name, email and conversion counts.

Files

  • .github/workflows/examples.yml: the workflow.
  • .github/ci/ci-host.targets and .github/ci/CiHost.cs: the CI host, CI only.
  • .github/ci/check_csharp.py: runs and checks the examples and the README blocks, with the Python 3 standard library only. Its docstring says how to run it locally.
  • Examples/TestFiles/test.html and Examples/TestFiles/test.xlsx: samples for the HTML and Excel README blocks.

Not changed: README.md, the library, CHANGELOG.md, RELEASING.md and build.yml.

🤖 Generated with Claude Code

A new workflow, examples.yml, runs on ubuntu-latest and macos-latest for pull
requests, pushes to main, every Monday and on demand:

- builds the solution and runs all 21 examples against https://stag-v2.convertapi.com;
- makes a console project of each of the 44 C# blocks in README.md, referencing
  the library source, and builds them all (fragments are spliced into the quick
  start program);
- runs the quick start and the blocks under the "... in C#" task headings with
  sample files from Examples/TestFiles;
- fails when a program fails, prints a caught API error, saves nothing, or
  prints the token or a Secret or ApiKey field.

The programs stay as people copy them: MSBuild imports
.github/ci/staging-host.targets only when CI sets CustomAfterMicrosoftCommonTargets,
and it compiles in StagingHost.cs, a module initializer that sets
ConvertApi.ApiBaseUri before Main. A failed run is retried twice, 30 s apart.

Examples/TestFiles gains test.html and test.xlsx for the HTML and Excel
README snippets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 09:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Quoted JSON credentials can bypass detection and leak into public logs, while a leftover staging diagnostic lets text-extraction checks pass without any extracted text.

2 open issues
What changed in this PR

Adds staging-backed CI checks for the SDK examples and README C# snippets without changing normal builds or library behavior.

Changes:

  • Adds an Ubuntu/macOS workflow for pull requests, main pushes, weekly runs, and manual runs.
  • Injects a CI-only staging initializer into executable projects.
  • Adds example execution, README compilation and runtime checks, retries, log safeguards, and run summaries.
File Description
Examples/​TestFiles/​test.html Adds sample HTML input for README conversions.
.github/​workflows/​examples.yml Orchestrates builds and staging runs on Ubuntu and macOS.
.github/​ci/​StagingHost.cs Sets the staging endpoint and prints API failure details.
.github/​ci/​staging-host.targets Injects the initializer into non-test executable projects.
.github/​ci/​check_csharp.py Builds README snippets and checks program execution, output, and credential leaks.

Review effort: Balanced

Comment thread .github/ci/check_csharp.py Resolved
Comment thread .github/ci/check_csharp.py Resolved
Run the examples and the README code against the API people use, with a
token kept for CI, instead of the staging API:

- the programs call https://v2.convertapi.com, the SDK's default; the CI
  host (CiHost.cs, was StagingHost.cs) no longer changes the address;
- CiHost.cs records every uploaded and result file and deletes it from the
  server when the program exits, instead of leaving it for 3 hours;
- the token comes from the CONVERTAPI_CI_TOKEN secret, an Actions and a
  Dependabot secret;
- the workflow runs for changes to ConvertApi/, Examples/, README.md,
  global.json or the CI files, every Monday and on demand; pull requests
  from forks are skipped;
- the account details that RetrieveUserInformation prints stay out of the
  public log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 09:19
@tomasr78 tomasr78 changed the title ci: run the examples and the README C# code against the staging API ci: run the examples and the README C# code against the API Nov 11, 2026
A JSON line such as "Secret": "..." has a quote between the key and the
colon, so the pattern missed it, both in the leak check and in the log
printer. Allow an optional quote after the key, and the same for the
account fields the log hides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Quoted JSON credentials can bypass detection and redaction, exposing secrets in public CI logs, while the workflow incorrectly targets production and skips required fork build checks.

4 open issues
1 resolved since last review

Review effort: Balanced

Comment thread .github/ci/check_csharp.py Resolved
Comment thread .github/workflows/examples.yml Resolved
Comment thread .github/workflows/examples.yml Resolved
Copilot AI balanced review requested due to automatic review settings November 11, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Detected multiline credentials can still appear in public logs, and timed-out child processes can continue making paid API calls and writing files during retries.

3 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Terminate child processes on timeout before retrying

.github/​ci/​check_csharp.py:186

On timeout, subprocess.run() kills only its immediate process, but both callers use dotnet run, which launches the example as a child. That child can continue making paid API calls and writing files while a retry resets the output folder or starts another run. Start a separate process group and terminate and reap it before retrying, or run the built program directly.

2 resolved since last review

Review effort: Balanced

Comment thread .github/ci/check_csharp.py Resolved
- When a run's output contains the token or a Secret or ApiKey field, none
  of it is printed: a value can sit on a line of its own after its key.
- A run that times out is stopped with its whole process group. `dotnet run`
  starts the program as its own child, which could otherwise go on calling
  the API while the next attempt runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 09:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues block the added live API checks for .NET SDK examples and README C# snippets.

No open issues 🎉

3 resolved since last review

Review effort: Balanced

@tomasr78
tomasr78 merged commit 3e603d4 into main Nov 11, 2026
4 of 6 checks passed
@tomasr78
tomasr78 deleted the ci-examples-staging branch November 11, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants