From 8be7fbd6a09b0c158d50e344d7b37837c4cf780b Mon Sep 17 00:00:00 2001 From: Stephan van Stekelenburg Date: Sun, 9 Aug 2026 12:00:00 +0200 Subject: [PATCH 1/2] Add CI, releases, and contributor docs --- .github/workflows/ci.yml | 163 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 83 +++++++++++++++++ CONTRIBUTING.md | 73 +++++++++++++++ README.md | 112 ++++++++++++++--------- 4 files changed, 390 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d644214 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,163 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: "1" + DOTNET_NOLOGO: "1" + D2_VERSION: 0.7.1 + +jobs: + build-and-test: + name: Build and test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up .NET + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 + with: + dotnet-version: 10.0.x + + - name: Restore + run: dotnet restore d2lang-cs.sln + + - name: Build + run: dotnet build d2lang-cs.sln --configuration Release --no-restore -p:ContinuousIntegrationBuild=true + + - name: Install pinned D2 CLI + if: runner.os == 'Linux' + shell: bash + run: | + archive="$RUNNER_TEMP/d2-v${D2_VERSION}-linux-amd64.tar.gz" + install_dir="$RUNNER_TEMP/d2-cli" + curl --proto '=https' --tlsv1.2 --location --fail --silent --show-error \ + "https://github.com/d2lang/d2/releases/download/v${D2_VERSION}/d2-v${D2_VERSION}-linux-amd64.tar.gz" \ + --output "$archive" + echo "eb172adf59f38d1e5a70ab177591356754ffaf9bebb84e0ca8b767dfb421dad7 $archive" | sha256sum --check --strict + mkdir -p "$install_dir" + tar -xzf "$archive" --strip-components=1 -C "$install_dir" + echo "$install_dir/bin" >> "$GITHUB_PATH" + + - name: Test with coverage + run: dotnet test test/Tests.csproj --configuration Release --no-build --logger "trx;LogFileName=tests.trx" --collect "XPlat Code Coverage" --results-directory artifacts/test-results/${{ runner.os }} + + - name: Add coverage summary + if: always() + shell: pwsh + run: | + $coveragePath = Get-ChildItem "artifacts/test-results/${{ runner.os }}" -Filter coverage.cobertura.xml -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -eq $coveragePath) { + "## Coverage (${{ runner.os }})`n`nCoverage report was not produced." | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append + exit 0 + } + + [xml] $coverage = Get-Content $coveragePath.FullName + $lineRate = [double]::Parse($coverage.coverage.'line-rate', [Globalization.CultureInfo]::InvariantCulture) + $branchRate = [double]::Parse($coverage.coverage.'branch-rate', [Globalization.CultureInfo]::InvariantCulture) + $linePercent = [Math]::Round($lineRate * 100, 2) + $branchPercent = [Math]::Round($branchRate * 100, 2) + $summary = "## Coverage (${{ runner.os }})`n`n| Metric | Coverage |`n| --- | ---: |`n| Lines | $linePercent% |`n| Branches | $branchPercent% |" + $summary | Out-File -FilePath "artifacts/test-results/${{ runner.os }}/coverage-summary.md" + $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append + + - name: Upload test results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-results-${{ runner.os }} + path: artifacts/test-results/${{ runner.os }}/**/*.trx + if-no-files-found: warn + retention-days: 14 + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-${{ runner.os }} + path: | + artifacts/test-results/${{ runner.os }}/**/coverage.cobertura.xml + artifacts/test-results/${{ runner.os }}/coverage-summary.md + if-no-files-found: warn + retention-days: 14 + + - name: Validate generated D2 syntax + if: runner.os == 'Linux' + shell: bash + run: | + d2 version + dotnet run --project example/cli/d2-sample-cli.csproj --configuration Release --no-build > artifacts/example.d2 + d2 validate artifacts/example.d2 + + package: + name: Validate NuGet package + needs: build-and-test + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up .NET + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 + with: + dotnet-version: 10.0.x + + - name: Restore + run: dotnet restore src/d2lang-cs.csproj + + - name: Pack with package validation + run: dotnet pack src/d2lang-cs.csproj --configuration Release --no-restore --output artifacts/packages -p:ContinuousIntegrationBuild=true -p:EnablePackageValidation=true + + - name: Inspect package contents + shell: bash + run: | + package_path=$(find artifacts/packages -maxdepth 1 -name '*.nupkg' ! -name '*.snupkg' -print -quit) + test -n "$package_path" + unzip -l "$package_path" | tee artifacts/package-contents.txt + grep -q 'lib/net10.0/d2lang-cs.dll' artifacts/package-contents.txt + grep -q 'README.md' artifacts/package-contents.txt + grep -q 'd2_logo.png' artifacts/package-contents.txt + + - name: Install package in a clean consumer project + shell: bash + run: | + dotnet new console --framework net10.0 --output artifacts/package-smoke --no-restore + dotnet add artifacts/package-smoke/package-smoke.csproj package d2lang-cs --source "$PWD/artifacts/packages" + dotnet build artifacts/package-smoke/package-smoke.csproj --configuration Release --no-restore + + - name: Upload package + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: nuget-package + path: | + artifacts/packages/*.nupkg + artifacts/package-contents.txt + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b6f0ae0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,83 @@ +name: Publish NuGet package + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +concurrency: + group: nuget-publish + cancel-in-progress: false + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: "1" + DOTNET_NOLOGO: "1" + +jobs: + publish: + name: Build, validate, and publish + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: nuget.org + + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up .NET + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 + with: + dotnet-version: 10.0.x + + - name: Resolve and validate package version + id: package + shell: bash + run: | + version="${GITHUB_REF_NAME#v}" + + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]]; then + echo "Version '$version' is not a supported semantic version." >&2 + exit 1 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Restore + run: dotnet restore d2lang-cs.sln + + - name: Test + run: dotnet test test/Tests.csproj --configuration Release --no-restore -p:ContinuousIntegrationBuild=true + + - name: Pack with package validation + run: >- + dotnet pack src/d2lang-cs.csproj + --configuration Release + --no-restore + --output artifacts/packages + -p:PackageVersion=${{ steps.package.outputs.version }} + -p:ContinuousIntegrationBuild=true + -p:EnablePackageValidation=true + -p:IncludeSymbols=true + -p:SymbolPackageFormat=snupkg + + - name: Upload release packages + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: d2lang-cs-${{ steps.package.outputs.version }} + path: artifacts/packages/*.*nupkg + if-no-files-found: error + retention-days: 30 + + - name: Publish to NuGet.org + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: >- + dotnet nuget push "artifacts/packages/*.nupkg" + --api-key "$NUGET_API_KEY" + --source https://api.nuget.org/v3/index.json + --skip-duplicate diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6bf8876 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing to d2lang-cs + +Thanks for helping improve `d2lang-cs`. Focused pull requests with tests are the easiest to review and release safely. + +## Development setup + +Install the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). The D2 CLI is optional for normal library development and required when checking generated D2 syntax locally. CI currently uses D2 CLI `v0.7.1`. + +Fork and clone the repository, then create a branch from `main`: + +```bash +git switch main +git pull --ff-only +git switch -c feature/short-description +``` + +Restore, build, and test from the repository root: + +```bash +dotnet restore d2lang-cs.sln +dotnet build d2lang-cs.sln --configuration Release --no-restore +dotnet test test/Tests.csproj --configuration Release --no-build +``` + +Create the NuGet package with SDK package validation enabled: + +```bash +dotnet pack src/d2lang-cs.csproj \ + --configuration Release \ + --output artifacts/packages \ + -p:EnablePackageValidation=true +``` + +## Validate generated D2 + +Install the [D2 CLI](https://d2lang.com/tour/install/), run the example, and validate its output: + +```bash +dotnet run --project example/cli/d2-sample-cli.csproj \ + --configuration Release > /tmp/d2lang-cs-example.d2 +d2 validate /tmp/d2lang-cs-example.d2 +``` + +When changing serialization, add tests for both the exact emitted source and parser acceptance. Include cases with reserved characters, quotes, whitespace, multiline text, URLs, and nested containers where relevant. + +## Pull requests + +- Keep changes focused on one concern. +- Add or update tests for behavior changes. +- Update the README when public behavior, requirements, or compatibility changes. +- Avoid unrelated formatting or generated-file changes. +- Run the release build, tests, package validation, and relevant D2 validation before requesting review. +- Call out intentional compatibility or output-format changes in the description. + +Pull requests run clean builds and tests on Linux and Windows. CI also publishes test results, Cobertura coverage artifacts, a coverage summary, a validated NuGet package artifact, and the example D2 source validation result. + +## Release process + +Releases are published by `.github/workflows/release.yml`. Maintainers should: + +1. Confirm the intended commit is on `main` and CI is green. +2. Confirm the `nuget.org` GitHub environment is protected as desired and contains a `NUGET_API_KEY` secret scoped to the `d2lang-cs` package. +3. Choose an unused semantic version such as `1.2.3` or `1.2.3-rc.1`. +4. Create an annotated `v`-prefixed tag and push it: + + ```bash + git tag -a v1.2.3 -m "d2lang-cs 1.2.3" + git push origin v1.2.3 + ``` + +5. Review the `Publish NuGet package` workflow and its package artifact before confirming the package on NuGet.org. + +The workflow derives `PackageVersion` from the tag, repeats tests, enables SDK package validation, creates `.nupkg` and `.snupkg` artifacts, and publishes with `dotnet nuget push`. Publishing uses read-only repository permissions and exposes `NUGET_API_KEY` only to the final push step. Duplicate versions are skipped safely, but NuGet package versions are immutable; use a new version if published contents need to change. diff --git a/README.md b/README.md index 61802be..ba4bef2 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,50 @@ # d2lang-cs -![Banner](docs/assets/img/banner.png) +![d2lang-cs banner](docs/assets/img/banner.png) -An unofficial interface for building [D2](https://github.com/terrastruct/d2) diagram files in C# and dotnet. +`d2lang-cs` is an unofficial .NET library for constructing [D2](https://d2lang.com/) diagram source from C#. -# Installation +The library produces D2 text; rendering that text to SVG, PNG, PDF, or another output format is handled separately by the [D2 CLI](https://d2lang.com/tour/install/). + +## Installation ```bash dotnet add package d2lang-cs ``` -# Usage +The current package targets .NET 10. Applications only need the D2 CLI if they also want to validate or render the generated source. + +## Quick start ```csharp using d2; var umbrella = new D2Shape("alphabet", "Alphabet Inc", Shape.Rectangle); -var company = new D2Shape("google", null, Shape.Rectangle); - -company.Add(new D2Shape("gmail", "Gmail", Shape.Rectangle)); -company.Add(new D2Shape("meet", "Meet", Shape.Rectangle)); -company.Add(new D2Shape("deepmind", "DeepMind", Shape.Rectangle)); +var company = new D2Shape("google", null, Shape.Rectangle) +{ + new D2Shape("gmail", "Gmail", Shape.Rectangle), + new D2Shape("meet", "Meet", Shape.Rectangle), + new D2Shape("deepmind", "DeepMind", Shape.Rectangle), +}; company.Icon = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png"; -var connection = new D2Connection(company.Name, umbrella.Name, Direction.TO, "BELONGS_TO"); +var connection = new D2Connection( + company.Name, + umbrella.Name, + Direction.TO, + "BELONGS_TO"); -var diagram = new D2Diagram(new[] { umbrella, company }, new[] { connection }); +var diagram = new D2Diagram( + new[] { umbrella, company }, + new[] { connection }); -Console.WriteLine(diagram.ToString()); +Console.WriteLine(diagram); ``` -# D2 Output -```d2-lang +This produces: + +```d2 alphabet: Alphabet Inc { shape: rectangle } @@ -52,35 +64,53 @@ google: { google -> alphabet: BELONGS_TO ``` -# Diagram Output -![Diagram](docs/assets/img/diagram.png) - -# Documentation -## Supported -- [x] Shapes (nodes) -- [x] Connections (edges) -- [x] Styles -- [x] Containers (nodes/links in nodes) -- [x] Arrow directions -- [x] Markdown / latex / block strings / code in shapes -- [x] Shape icons -- [ ] SQL table shapes -- [ ] Class shapes -- [ ] Comments - -# Inspiration & Thanks -- [Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js) -- [MrBlenny/py-d2](https://github.com/MrBlenny/py-d2) +![Rendered example diagram](docs/assets/img/diagram.png) + +To validate or render saved output with D2: + +```bash +d2 validate diagram.d2 +d2 diagram.d2 diagram.svg +``` + +## Supported features -# Thank me! -If you like what I'm doing and you would like to thank me, please consider: +- Shapes and containers +- Connections with forward, reverse, bidirectional, and undirected edges +- Shape styles +- Markdown, LaTeX, block strings, and code text in shapes +- Shape icons +- `near` positioning - -Buy Me A Coffee! - +The `Shape` type exposes D2's `sql_table`, `class`, and `sequence_diagram` shape values, but the library does not yet provide specialized models for their fields, methods, columns, or sequence semantics. -Thank you for your support! +## Compatibility and support + +- **.NET:** the current package targets `net10.0`. +- **D2 syntax checks:** CI validates the example's generated source with D2 CLI `v0.7.1`. +- **Project status:** this is a community-maintained integration and is not an official D2 project. +- **Issues:** use [GitHub Issues](https://github.com/Stephanvs/d2lang-cs/issues) for reproducible bugs and focused feature requests. + +Because D2 has syntax with reserved characters, treat names and labels as trusted D2 source until the planned serialization work is complete. When values can contain punctuation, quotes, newlines, URL fragments, or other user-controlled text, validate the generated document with the D2 CLI before rendering or publishing it. + +## Roadmap + +The next areas under consideration are: + +- Centralized quoting, escaping, and serialization +- Parser-backed tests for generated diagrams +- A broader, extensible property model for newer D2 features +- Typed support for SQL tables, classes, and sequence diagrams +- Wider .NET target-framework compatibility +- More consistent immutable or builder-style APIs + +The roadmap is directional rather than a release commitment. Contributions are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md) for the development and release checks. + +## Inspiration and thanks + +- [Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js) +- [MrBlenny/py-d2](https://github.com/MrBlenny/py-d2) -
+If this project helps you, you can [support its maintenance on Buy Me a Coffee](https://www.buymeacoffee.com/stephanvs). -Copyright © 2023 [Stephan van Stekelenburg](https://stephanvs.com) - Provided under [MIT License](LICENSE) +Copyright © 2023 Stephan van Stekelenburg. Provided under the [MIT License](LICENSE). From 03bea9c69cba7b1cfa88b5d65d8e6cceb38d6b02 Mon Sep 17 00:00:00 2001 From: Stephan van Stekelenburg Date: Sun, 9 Aug 2026 12:17:49 +0200 Subject: [PATCH 2/2] Keep CI documentation focused --- README.md | 112 ++++++++++++++++++++---------------------------------- 1 file changed, 41 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index ba4bef2..61802be 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,38 @@ # d2lang-cs -![d2lang-cs banner](docs/assets/img/banner.png) +![Banner](docs/assets/img/banner.png) -`d2lang-cs` is an unofficial .NET library for constructing [D2](https://d2lang.com/) diagram source from C#. +An unofficial interface for building [D2](https://github.com/terrastruct/d2) diagram files in C# and dotnet. -The library produces D2 text; rendering that text to SVG, PNG, PDF, or another output format is handled separately by the [D2 CLI](https://d2lang.com/tour/install/). - -## Installation +# Installation ```bash dotnet add package d2lang-cs ``` -The current package targets .NET 10. Applications only need the D2 CLI if they also want to validate or render the generated source. - -## Quick start +# Usage ```csharp using d2; var umbrella = new D2Shape("alphabet", "Alphabet Inc", Shape.Rectangle); -var company = new D2Shape("google", null, Shape.Rectangle) -{ - new D2Shape("gmail", "Gmail", Shape.Rectangle), - new D2Shape("meet", "Meet", Shape.Rectangle), - new D2Shape("deepmind", "DeepMind", Shape.Rectangle), -}; +var company = new D2Shape("google", null, Shape.Rectangle); + +company.Add(new D2Shape("gmail", "Gmail", Shape.Rectangle)); +company.Add(new D2Shape("meet", "Meet", Shape.Rectangle)); +company.Add(new D2Shape("deepmind", "DeepMind", Shape.Rectangle)); company.Icon = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png"; -var connection = new D2Connection( - company.Name, - umbrella.Name, - Direction.TO, - "BELONGS_TO"); +var connection = new D2Connection(company.Name, umbrella.Name, Direction.TO, "BELONGS_TO"); -var diagram = new D2Diagram( - new[] { umbrella, company }, - new[] { connection }); +var diagram = new D2Diagram(new[] { umbrella, company }, new[] { connection }); -Console.WriteLine(diagram); +Console.WriteLine(diagram.ToString()); ``` -This produces: - -```d2 +# D2 Output +```d2-lang alphabet: Alphabet Inc { shape: rectangle } @@ -64,53 +52,35 @@ google: { google -> alphabet: BELONGS_TO ``` -![Rendered example diagram](docs/assets/img/diagram.png) - -To validate or render saved output with D2: - -```bash -d2 validate diagram.d2 -d2 diagram.d2 diagram.svg -``` - -## Supported features - -- Shapes and containers -- Connections with forward, reverse, bidirectional, and undirected edges -- Shape styles -- Markdown, LaTeX, block strings, and code text in shapes -- Shape icons -- `near` positioning - -The `Shape` type exposes D2's `sql_table`, `class`, and `sequence_diagram` shape values, but the library does not yet provide specialized models for their fields, methods, columns, or sequence semantics. - -## Compatibility and support - -- **.NET:** the current package targets `net10.0`. -- **D2 syntax checks:** CI validates the example's generated source with D2 CLI `v0.7.1`. -- **Project status:** this is a community-maintained integration and is not an official D2 project. -- **Issues:** use [GitHub Issues](https://github.com/Stephanvs/d2lang-cs/issues) for reproducible bugs and focused feature requests. - -Because D2 has syntax with reserved characters, treat names and labels as trusted D2 source until the planned serialization work is complete. When values can contain punctuation, quotes, newlines, URL fragments, or other user-controlled text, validate the generated document with the D2 CLI before rendering or publishing it. - -## Roadmap - -The next areas under consideration are: - -- Centralized quoting, escaping, and serialization -- Parser-backed tests for generated diagrams -- A broader, extensible property model for newer D2 features -- Typed support for SQL tables, classes, and sequence diagrams -- Wider .NET target-framework compatibility -- More consistent immutable or builder-style APIs +# Diagram Output +![Diagram](docs/assets/img/diagram.png) + +# Documentation +## Supported +- [x] Shapes (nodes) +- [x] Connections (edges) +- [x] Styles +- [x] Containers (nodes/links in nodes) +- [x] Arrow directions +- [x] Markdown / latex / block strings / code in shapes +- [x] Shape icons +- [ ] SQL table shapes +- [ ] Class shapes +- [ ] Comments + +# Inspiration & Thanks +- [Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js) +- [MrBlenny/py-d2](https://github.com/MrBlenny/py-d2) -The roadmap is directional rather than a release commitment. Contributions are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md) for the development and release checks. +# Thank me! +If you like what I'm doing and you would like to thank me, please consider: -## Inspiration and thanks + +Buy Me A Coffee! + -- [Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js) -- [MrBlenny/py-d2](https://github.com/MrBlenny/py-d2) +Thank you for your support! -If this project helps you, you can [support its maintenance on Buy Me a Coffee](https://www.buymeacoffee.com/stephanvs). +
-Copyright © 2023 Stephan van Stekelenburg. Provided under the [MIT License](LICENSE). +Copyright © 2023 [Stephan van Stekelenburg](https://stephanvs.com) - Provided under [MIT License](LICENSE)