From 04595f553a94c18bb405d1aefdbe0b5d9b9a99b4 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:13:09 +0000 Subject: [PATCH 1/7] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/link-foundation/lino-objects-codec/issues/25 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..d78678e --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-05-03T11:13:09.664Z for PR creation at branch issue-25-76b0e5ea19d6 for issue https://github.com/link-foundation/lino-objects-codec/issues/25 \ No newline at end of file From c9ad079c01b80e96d4d9b1fb06c8ffd7410bfb69 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:23:45 +0000 Subject: [PATCH 2/7] docs(case-study): add issue-25 publication-state analysis Captures the registry probe (npm 200, PyPI/crates.io/NuGet 404), the four relevant CI runs as JSON evidence, and a root-cause write-up of the silent publish-skip pattern that left Rust and C# auto-releases green while publishing nothing, plus the git-tag gate that prevents retries. --- docs/case-studies/issue-25/README.md | 254 ++++++++++++++++++ .../issue-25/registry-crates-not-found.txt | 1 + docs/case-studies/issue-25/registry-npm.json | 1 + .../issue-25/registry-nuget-not-found.txt | 3 + .../issue-25/registry-presence.sh | 8 + .../issue-25/registry-pypi-not-found.txt | 1 + .../issue-25/run-20804476989.json | 1 + .../issue-25/run-20804477002.json | 1 + .../issue-25/run-20804477007.json | 1 + .../issue-25/run-20828012412.json | 1 + 10 files changed, 272 insertions(+) create mode 100644 docs/case-studies/issue-25/README.md create mode 100644 docs/case-studies/issue-25/registry-crates-not-found.txt create mode 100644 docs/case-studies/issue-25/registry-npm.json create mode 100644 docs/case-studies/issue-25/registry-nuget-not-found.txt create mode 100755 docs/case-studies/issue-25/registry-presence.sh create mode 100644 docs/case-studies/issue-25/registry-pypi-not-found.txt create mode 100644 docs/case-studies/issue-25/run-20804476989.json create mode 100644 docs/case-studies/issue-25/run-20804477002.json create mode 100644 docs/case-studies/issue-25/run-20804477007.json create mode 100644 docs/case-studies/issue-25/run-20828012412.json diff --git a/docs/case-studies/issue-25/README.md b/docs/case-studies/issue-25/README.md new file mode 100644 index 0000000..532bbac --- /dev/null +++ b/docs/case-studies/issue-25/README.md @@ -0,0 +1,254 @@ +# Case Study: Silent Publication Failures and Missing Package Visibility + +**Issue:** [#25](https://github.com/link-foundation/lino-objects-codec/issues/25) +**Pull Request:** [#26](https://github.com/link-foundation/lino-objects-codec/pull/26) +**Date Investigated:** 2026-05-03 +**Branch:** `issue-25-76b0e5ea19d6` + +## Executive Summary + +The repository advertises four language packages (Python, JavaScript, Rust, C#) but only the npm +package was actually published to its registry. The Rust and C# CI/CD pipelines reported a green +"Auto Release" job while silently skipping the publish step because their secret-presence guards +exit `0` when the registry token is missing. The Python pipeline did fail (PyPI publish step), but +because the Rust/C# git-tag gate considers the local tag the source of truth, subsequent pushes +permanently skip republishing — even though the packages do not exist on the registry. + +In addition, neither the root `README.md` nor any of the per-language READMEs carry a +registry-version badge, so a reader has no fast way to see which languages are published, at which +version, or whether publication is broken. + +## Verified Package State (snapshot 2026-05-03) + +| Language | Registry | Package id | Latest published | Local version | Evidence | +|----------|-----------|-------------------------|------------------|---------------|-----------------------------------------| +| JS | npm | `lino-objects-codec` | 0.3.1 | 0.3.1 | `registry-npm.json` (HTTP 200) | +| Python | PyPI | `lino-objects-codec` | *not found* | 0.2.0 | `registry-pypi-not-found.txt` (HTTP 404)| +| Rust | crates.io | `lino-objects-codec` | *not found* | 0.2.0 | `registry-crates-not-found.txt` (HTTP 404)| +| C# | NuGet | `Lino.Objects.Codec` | *not found* | 0.2.0 | `registry-nuget-not-found.txt` (HTTP 404)| + +Local `git tag` listing on the same date: + +``` +csharp-v0.2.0 +rust-v0.2.0 +v0.1.1 +v0.3.0 +v0.3.1 +``` + +GitHub releases on the same date: + +``` +C# 0.2.0 csharp-v0.2.0 2026-01-08 +Rust 0.2.0 rust-v0.2.0 2026-01-08 +0.3.1 v0.3.1 2026-01-08 (JS) +0.3.0 v0.3.0 2026-01-08 (JS) +0.1.1 v0.1.1 2025-12-21 (legacy) +``` + +There is **no Python release and no Python tag** at all — the very first PyPI publish failed and +nothing since has retried. + +## Timeline of Events + +The release pipelines were introduced together in early January 2026. The relevant CI runs are +captured as JSON in this directory. + +| When (UTC) | Event | Run id | +|--------------------|--------------------------------------------------------------------------------------------|---------------| +| 2026-01-08 03:29 | First post-pipeline push to `main` triggers Python, Rust, C# auto-release in parallel. | - | +| 2026-01-08 03:29 | **Python Auto Release fails** at `Publish to PyPI` step. No tag created, no release made. | 20804476989 | +| 2026-01-08 03:29 | **Rust Auto Release fails** at `Create GitHub Release`, but `Publish to crates.io` step is reported `success`. | 20804477002 | +| 2026-01-08 03:29 | **C# Auto Release fails** at `Create GitHub Release`, but `Publish to NuGet` step is reported `success`. | 20804477007 | +| 2026-01-08 18:48 | Push to `main` retriggers Rust auto-release. `Create GitHub Release` fails again (run 20828012412). | 20828012412 | +| 2026-01-08 18:52 | Issue [#22](https://github.com/link-foundation/lino-objects-codec/issues/22) is filed for the GitHub-release failure. | - | +| 2026-01-08 19:20 | PR [#23](https://github.com/link-foundation/lino-objects-codec/pull/23) merges fix for `yargs` reserved-word issue. Rust 0.2.0 and C# 0.2.0 GitHub releases are created — but their packages were never actually pushed to crates.io / NuGet. | - | +| 2026-05-03 | Issue [#25](https://github.com/link-foundation/lino-objects-codec/issues/25) requests visible publish-status badges and per-language READMEs. | - | + +The four `run-*.json` files in this folder hold the raw step outcomes that prove the analysis above +(GitHub log retention had already GC'd the textual logs by the time of investigation, so the JSON +step list is the highest-fidelity evidence still available). + +## Root Cause Analysis + +### Cause 1 — Silent skip on missing token (Rust and C#) + +`.github/workflows/rust.yml` (lines 325-335) and `.github/workflows/csharp.yml` (lines 228-241) wrap +their publish step in: + +```bash +if [ -z "$CARGO_REGISTRY_TOKEN" ]; then + echo "::warning::... not set, skipping publish to crates.io" +else + cargo publish +fi +``` + +If the secret is missing (or not exposed to the job — typical for org-level secrets that need to be +mapped explicitly) the script prints a warning and exits `0`. The job is then green even though +nothing was published. Because the same workflow goes on to create a Git tag and a GitHub release, +the next run sees "tag exists" and never retries the publish — failure is permanently masked. + +### Cause 2 — Git-tag gate confuses "released" with "published" (all three) + +Each non-JS auto-release job decides whether to publish by checking the local Git tag: + +```yaml +if git rev-parse "rust-v$CURRENT_VERSION" >/dev/null 2>&1; then + echo "should_release=false" +fi +``` + +(Equivalent code in `python.yml` line 213-220 and `csharp.yml` line 213-219.) + +That gate conflates two unrelated states: +- *We have already created a git tag for this version.* +- *The package binary for this version exists on the public registry.* + +After Issue #22 was fixed and 0.2.0 tags were created, the registries still had no package, but the +workflow now refuses to retry. The only way out today is a manual version bump. + +### Cause 3 — `pypa/gh-action-pypi-publish` first-publish requirements (Python) + +For the Python failure on 2026-01-08 the PyPI step actually exited non-zero (correct behaviour). The +likeliest cause — the project does not exist yet on PyPI and there is no Trusted Publisher +configured for the repo — is consistent with the registry state today (HTTP 404 for the project +JSON). The cure is the same as for Rust/C#: do not rely on a single first-time publish; use a +publish step that can be re-run idempotently, **and** make sure the secret/trusted-publisher path is +configured. + +### Cause 4 — No public visibility of publish status + +Neither the root `README.md` nor any per-language `README.md` carries a registry-version badge. A +reader cannot tell which packages are actually on the registry, what version, or that three of four +languages are silently broken. The CI badges currently in the README report the *workflow* status, +not the *registry* status, so they were green on 2026-01-08 even while crates.io/NuGet/PyPI received +nothing. + +## Requirements From the Issue + +The issue asked for the following deliverables. All are addressed in PR #26: + +1. All packages should be published to their respective registries (PyPI / npm / crates.io / NuGet). +2. When publication fails, provide clear visible feedback as a badge for each package. +3. Per-language READMEs with badges for the latest package version. +4. Root `README.md` should contain badges of all packages with direct links to the registries. +5. GitHub releases should be separate per language, with prefixed tags (`python-v…`, `rust-v…`, + `csharp-v…`, plus the JS default `v…`). +6. Apply best practices from the four AI-driven-development pipeline templates and report any + shared issue back to the templates. +7. Compile data and analysis to `docs/case-studies/issue-25/` (this folder). +8. If the same defect exists in a template, file an issue there too. + +## Proposed Solution Plan + +### Step A — Make the publish step honest (Rust & C#) + +Replace the silent-skip wrappers with code that fails the job when the secret is missing **and** is +idempotent on re-runs: + +- Rust: switch to `cargo publish --token "$CARGO_REGISTRY_TOKEN"` and fail when the token is unset. + The Rust pipeline template's `scripts/publish-crate.rs` already handles the + `already uploaded` / `already exists` cases by mapping them to a deliberate exit code so the + workflow can keep going. +- C#: keep `dotnet nuget push --skip-duplicate` (already used) but fail when `NUGET_API_KEY` is + unset. + +### Step B — Replace the git-tag gate with a registry probe + +Decide whether to publish by querying the registry directly: + +- crates.io: `curl -fsS https://crates.io/api/v1/crates//` (HTTP 200 = already published). +- NuGet: `curl -fsS https://api.nuget.org/v3-flatcontainer///.nuspec`. +- PyPI: `curl -fsS https://pypi.org/pypi///json`. + +This makes the pipeline self-healing: if a publish silently fails, the next push will retry. (This +is the exact pattern used by `rust-ai-driven-development-pipeline-template/scripts/check-release-needed.rs`.) + +### Step C — Per-language registry-version badges + +Add to the root `README.md`: + +```markdown +[![npm](https://img.shields.io/npm/v/lino-objects-codec?label=npm)](https://www.npmjs.com/package/lino-objects-codec) +[![PyPI](https://img.shields.io/pypi/v/lino-objects-codec?label=PyPI)](https://pypi.org/project/lino-objects-codec/) +[![crates.io](https://img.shields.io/crates/v/lino-objects-codec?label=crates.io)](https://crates.io/crates/lino-objects-codec) +[![NuGet](https://img.shields.io/nuget/v/Lino.Objects.Codec?label=NuGet)](https://www.nuget.org/packages/Lino.Objects.Codec) +``` + +shields.io renders these badges from the live registry, so a 404 visibly appears as `not found`. +That gives the issue's requested "clear visible feedback" without writing any custom CI status logic. + +Add the same badge for each language's README (only the relevant one). + +### Step D — Configure repository secrets / trusted publisher + +Out-of-band but essential: +- Ensure `CARGO_REGISTRY_TOKEN` (or `CARGO_TOKEN`) is set as a repository secret. +- Ensure `NUGET_API_KEY` is set. +- Configure PyPI Trusted Publisher for the repo + workflow + environment, **or** add a + `TWINE_PASSWORD`/`PYPI_API_TOKEN` and pass it via `password:` to the action. + +### Step E — Per-language tag prefixes (already partially in place) + +`python.yml` already passes `--tag-prefix python-v` to `create_github_release.py`, `rust.yml` +passes `rust-v`, `csharp.yml` passes `csharp-v`, and `js.yml` continues to use the default `v`. +This step is mostly verifying that nothing else still creates an unprefixed tag and that the +documentation reflects the per-language tagging convention. + +### Step F — Re-publish 0.2.0 (one-time) + +Because tags were already minted, the Step B fix needs to find that the registry is missing the +package and republish. If the workflow's first re-run with the fixed gate still fails (e.g., +because the secret hasn't been added yet) the case-study will be updated and the maintainer +prompted via PR comment. + +## Component / Library Reuse + +Where possible the fix uses existing tools rather than new bespoke scripts: + +- `pypa/gh-action-pypi-publish@release/v1` (already used) — supports both Trusted Publishers and + `password:` token authentication. +- `dotnet nuget push --skip-duplicate` (already used) — idempotent. +- `cargo publish` natively returns `crates.io: status 422 already exists` which the Rust pipeline + template's `publish-crate.rs` script maps to a non-fatal outcome. +- shields.io badges — no code at all, just URLs. +- `actions/checkout@v4`, `actions/setup-*@v…` — unchanged. + +## Upstream Reports + +The same publish-token-silent-skip pattern exists in two of the upstream templates and should be +reported there: + +- `link-foundation/rust-ai-driven-development-pipeline-template` — has both the silent skip and a + registry-probe script (`check-release-needed.rs`) that *is* used for "should we release" but not + for the publish-or-skip decision. Reporting the inconsistency. +- `link-foundation/csharp-ai-driven-development-pipeline-template` — silent-skip wrapper for + `NUGET_API_KEY`. +- The Python template uses `pypa/gh-action-pypi-publish` which fails loudly on missing + authentication, so no upstream report is needed for that one. + +The template-issue creation, where appropriate, is done as part of finalising PR #26. + +## Lessons Learned + +1. **A green CI badge is not proof of publication.** Always pair workflow badges with registry + badges when publishing artefacts. +2. **`exit 0` is dangerous when guarding side-effects.** "Skip and warn" patterns hide failures + forever in pipelines that key off their own previous output (git tags here). +3. **Decide based on the artefact, not its proxy.** The truth source for "did we publish" lives on + the registry; querying it is a one-liner and is naturally idempotent. +4. **Make first-publish part of CI verification.** A first publish is unique because the registry + project does not yet exist; ensure that scenario is exercised — once. + +## References + +- Issue #25 — All packages should be published, with visible badges +- Issue #22 / PR #23 — Earlier yargs reserved-word fix that explains the 18:48 failure +- `docs/case-studies/issue-22/README.md` — Format and prior-art for this case study +- shields.io endpoints used: `img.shields.io/npm/v`, `/pypi/v`, `/crates/v`, `/nuget/v` +- `rust-ai-driven-development-pipeline-template/scripts/publish-crate.rs` — model for idempotent + publish handling +- `rust-ai-driven-development-pipeline-template/scripts/check-release-needed.rs` — registry probe + pattern diff --git a/docs/case-studies/issue-25/registry-crates-not-found.txt b/docs/case-studies/issue-25/registry-crates-not-found.txt new file mode 100644 index 0000000..9315f35 --- /dev/null +++ b/docs/case-studies/issue-25/registry-crates-not-found.txt @@ -0,0 +1 @@ +{"errors":[{"detail":"crate `lino-objects-codec` does not exist"}]} \ No newline at end of file diff --git a/docs/case-studies/issue-25/registry-npm.json b/docs/case-studies/issue-25/registry-npm.json new file mode 100644 index 0000000..7a2b508 --- /dev/null +++ b/docs/case-studies/issue-25/registry-npm.json @@ -0,0 +1 @@ +{"_id":"lino-objects-codec","_rev":"4-4b9738e55c04b32dcc03995425c162f2","name":"lino-objects-codec","dist-tags":{"latest":"0.3.1"},"versions":{"0.1.0":{"name":"lino-objects-codec","version":"0.1.0","keywords":["links-notation","serialization","codec","object-graph","circular-references"],"author":{"name":"Link Foundation"},"license":"Unlicense","_id":"lino-objects-codec@0.1.0","maintainers":[{"name":"konard","email":"drakonard@gmail.com"}],"homepage":"https://github.com/link-foundation/lino-objects-codec#readme","bugs":{"url":"https://github.com/link-foundation/lino-objects-codec/issues"},"dist":{"shasum":"daa505c5c49d0ff97a946d34c2f0dea5ec062c7d","tarball":"https://registry.npmjs.org/lino-objects-codec/-/lino-objects-codec-0.1.0.tgz","fileCount":12,"integrity":"sha512-OgxUszPF4vQcExs+mUoH5tGrv2wLJFF/gcnJlHUdSW+MYGjICLvisJwQxH7nOF86HMd5vopLcXrd4joN80mcWQ==","signatures":[{"sig":"MEQCIG4hbnO9oykuLgBA0D2Ma1jnbRwFXIw4545GklvYOwepAiAkXosi/ipfG5DgW4yteXWIlAVgj+nVZCMYZ9qpf3BJ7Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":89340},"main":"./src/index.js","type":"module","engines":{"node":">=18.0.0"},"exports":{".":"./src/index.js"},"gitHead":"fef0a2886817ced771bc3364fbeb20f321bb5008","scripts":{"test":"node --test tests/*.test.js","example":"node examples/basic_usage.js"},"_npmUser":{"name":"konard","email":"drakonard@gmail.com"},"repository":{"url":"git+https://github.com/link-foundation/lino-objects-codec.git","type":"git"},"_npmVersion":"11.7.0","description":"A library to encode/decode objects to/from links notation","directories":{},"_nodeVersion":"20.19.4","dependencies":{"links-notation":"^0.11.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/lino-objects-codec_0.1.0_1766337755844_0.7054226864785889","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"lino-objects-codec","version":"0.1.1","keywords":["links-notation","serialization","codec","object-graph","circular-references"],"author":{"name":"Link Foundation"},"license":"Unlicense","_id":"lino-objects-codec@0.1.1","maintainers":[{"name":"konard","email":"drakonard@gmail.com"}],"homepage":"https://github.com/link-foundation/lino-objects-codec#readme","bugs":{"url":"https://github.com/link-foundation/lino-objects-codec/issues"},"dist":{"shasum":"545488f349b721204db80ed2cf8b970c138caef3","tarball":"https://registry.npmjs.org/lino-objects-codec/-/lino-objects-codec-0.1.1.tgz","fileCount":29,"integrity":"sha512-URX1MAhHyVga5EkpUqDTMkX1D+HYBW3spgKh6vCneQnzOMLn09XooQOuBy0apbfwlqY5qHt2fpvRdT75dBn2Qw==","signatures":[{"sig":"MEYCIQCGfK+solVX/U2xVPKM1etKskzr3ZCwqWZ1tIw0eLQ7+AIhALZoUf749S0F7/lEIZI+Emzd54tPT7J1Pf06Lfmr+mcC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/lino-objects-codec@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":133770},"main":"./src/index.js","type":"module","engines":{"node":">=18.0.0"},"exports":{".":"./src/index.js"},"gitHead":"ff88c8e8750809a3ae46976c583e700f52c6912b","scripts":{"lint":"eslint .","test":"node --test tests/*.test.js","check":"npm run lint && npm run format:check && npm run check:duplication","format":"prettier --write .","example":"node examples/basic_usage.js","prepare":"husky || true","lint:fix":"eslint . --fix","changeset":"changeset","format:check":"prettier --check .","changeset:status":"changeset status --since=origin/main","changeset:publish":"changeset publish","changeset:version":"node scripts/changeset-version.mjs","check:duplication":"jscpd ."},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:c4f7858a-8e18-43ae-82c3-7646eea9f1a6"}},"repository":{"url":"git+https://github.com/link-foundation/lino-objects-codec.git","type":"git"},"_npmVersion":"11.7.0","description":"A library to encode/decode objects to/from links notation","directories":{},"lint-staged":{"*.md":["prettier --write","prettier --check"],"*.{js,mjs,cjs}":["eslint --fix --max-warnings 0","prettier --write","prettier --check"]},"_nodeVersion":"22.21.1","dependencies":{"links-notation":"^0.11.0"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","jscpd":"^4.0.5","eslint":"^9.38.0","prettier":"^3.6.2","lint-staged":"^16.2.6","@changesets/cli":"^2.29.7","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4"},"_npmOperationalInternal":{"tmp":"tmp/lino-objects-codec_0.1.1_1766342955850_0.8227038116465892","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"lino-objects-codec","version":"0.3.0","keywords":["links-notation","serialization","codec","object-graph","circular-references"],"author":{"name":"Link Foundation"},"license":"Unlicense","_id":"lino-objects-codec@0.3.0","maintainers":[{"name":"konard","email":"drakonard@gmail.com"}],"homepage":"https://github.com/link-foundation/lino-objects-codec#readme","bugs":{"url":"https://github.com/link-foundation/lino-objects-codec/issues"},"dist":{"shasum":"f1f6555dcac092aa2f35a301be17ce1cb4d60021","tarball":"https://registry.npmjs.org/lino-objects-codec/-/lino-objects-codec-0.3.0.tgz","fileCount":29,"integrity":"sha512-UfztTIdq5A5rvYpk37oeACpMW5mI9g6ARG1PYLxjMKfnIngONw38Ckb2BZGAU14WvMfxeFjaCDd2rGZfnUFZ3A==","signatures":[{"sig":"MEYCIQD9c9DPVU+NrMSxneRJHJdAKrJ3enFzhAgFJF5Jbcf2OgIhAOlji2wIylF4/8mzuCt3beMqDaEXx2w+ACSNofPYQz66","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/lino-objects-codec@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":144004},"main":"./src/index.js","type":"module","engines":{"node":">=18.0.0"},"exports":{".":"./src/index.js"},"gitHead":"fb6e4dea4702040b084e391552a9a6915f0e8840","scripts":{"lint":"eslint .","test":"node --test tests/*.test.js","check":"npm run lint && npm run format:check && npm run check:duplication","format":"prettier --write .","example":"node examples/basic_usage.js","prepare":"husky || true","lint:fix":"eslint . --fix","changeset":"changeset","format:check":"prettier --check .","changeset:status":"changeset status --since=origin/main","changeset:publish":"changeset publish","changeset:version":"node scripts/changeset-version.mjs","check:duplication":"jscpd ."},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:c4f7858a-8e18-43ae-82c3-7646eea9f1a6"}},"repository":{"url":"git+https://github.com/link-foundation/lino-objects-codec.git","type":"git"},"_npmVersion":"11.7.0","description":"A library to encode/decode objects to/from links notation","directories":{},"lint-staged":{"*.md":["prettier --write","prettier --check"],"*.{js,mjs,cjs}":["eslint --fix --max-warnings 0","prettier --write","prettier --check"]},"_nodeVersion":"22.21.1","dependencies":{"links-notation":"^0.11.0"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","jscpd":"^4.0.5","eslint":"^9.38.0","prettier":"^3.6.2","lint-staged":"^16.2.6","@changesets/cli":"^2.29.7","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4"},"_npmOperationalInternal":{"tmp":"tmp/lino-objects-codec_0.3.0_1767843014721_0.6405481443121117","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"lino-objects-codec","version":"0.3.1","description":"A library to encode/decode objects to/from links notation","type":"module","main":"./src/index.js","exports":{".":"./src/index.js"},"scripts":{"test":"node --test tests/*.test.js","example":"node examples/basic_usage.js","lint":"eslint .","lint:fix":"eslint . --fix","format":"prettier --write .","format:check":"prettier --check .","check:duplication":"jscpd .","check":"npm run lint && npm run format:check && npm run check:duplication","prepare":"husky || true","changeset":"changeset","changeset:version":"node scripts/changeset-version.mjs","changeset:publish":"changeset publish","changeset:status":"changeset status --since=origin/main"},"keywords":["links-notation","serialization","codec","object-graph","circular-references"],"author":{"name":"Link Foundation"},"license":"Unlicense","repository":{"type":"git","url":"git+https://github.com/link-foundation/lino-objects-codec.git"},"dependencies":{"links-notation":"^0.11.0"},"engines":{"node":">=18.0.0"},"devDependencies":{"@changesets/cli":"^2.29.7","eslint":"^9.38.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","husky":"^9.1.7","jscpd":"^4.0.5","lint-staged":"^16.2.6","prettier":"^3.6.2"},"lint-staged":{"*.{js,mjs,cjs}":["eslint --fix --max-warnings 0","prettier --write","prettier --check"],"*.md":["prettier --write","prettier --check"]},"gitHead":"57b33900930e484f211863fb09d36188f38fcaeb","_id":"lino-objects-codec@0.3.1","bugs":{"url":"https://github.com/link-foundation/lino-objects-codec/issues"},"homepage":"https://github.com/link-foundation/lino-objects-codec#readme","_nodeVersion":"22.21.1","_npmVersion":"11.7.0","dist":{"integrity":"sha512-2zjFomVOc+TT9UDRBM9PvzuXiEV4b2uA6kL5yi180Y7QXpYOpTplRjbToUs6Zew1YLO+R+ik2SthOK88JBAMvg==","shasum":"76a0dd9a8a4da1ea71ff518a5747f6b012ecc77d","tarball":"https://registry.npmjs.org/lino-objects-codec/-/lino-objects-codec-0.3.1.tgz","fileCount":33,"unpackedSize":162729,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/lino-objects-codec@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCjjWEuL5mv3Isf4W70q3TXeZjPDI+zJDWSlCz7bgHKygIhAKKDx0pDpqrNLir6vi+lPOspEPkXvrgs+vFZnNbFT++2"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:c4f7858a-8e18-43ae-82c3-7646eea9f1a6"}},"directories":{},"maintainers":[{"name":"konard","email":"drakonard@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lino-objects-codec_0.3.1_1767898204071_0.8588365074519548"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-21T17:22:35.710Z","modified":"2026-01-08T18:50:04.552Z","0.1.0":"2025-12-21T17:22:35.998Z","0.1.1":"2025-12-21T18:49:15.996Z","0.3.0":"2026-01-08T03:30:14.906Z","0.3.1":"2026-01-08T18:50:04.211Z"},"bugs":{"url":"https://github.com/link-foundation/lino-objects-codec/issues"},"author":{"name":"Link Foundation"},"license":"Unlicense","homepage":"https://github.com/link-foundation/lino-objects-codec#readme","keywords":["links-notation","serialization","codec","object-graph","circular-references"],"repository":{"type":"git","url":"git+https://github.com/link-foundation/lino-objects-codec.git"},"description":"A library to encode/decode objects to/from links notation","maintainers":[{"name":"konard","email":"drakonard@gmail.com"}],"readme":"# lino-objects-codec (JavaScript)\n\nA JavaScript library for working with Links Notation format. This library provides:\n\n- Universal serialization/deserialization for JavaScript objects with circular reference support\n- JSON to Links Notation conversion utilities\n- Fuzzy matching utilities for string comparison\n\nThese tools enable easy implementation of higher-level features like:\n\n- [LinksNotationManager](https://github.com/konard/follow/blob/main/lino.lib.mjs) - Intermediate application data storage\n- [Q&A Database](https://github.com/konard/hh-job-application-automation/blob/main/src/qa-database.mjs) - Questions and answers database\n\n## Features\n\n- **Universal Serialization**: Encode JavaScript objects to Links Notation format\n- **Type Support**: Handle all common JavaScript types:\n - Basic types: `null`, `undefined`, `boolean`, `number`, `string`\n - Collections: `Array`, `Object`\n - Special number values: `NaN`, `Infinity`, `-Infinity`\n- **Circular References**: Automatically detect and preserve circular references\n- **Object Identity**: Maintain object identity for shared references\n- **UTF-8 Support**: Full Unicode string support using base64 encoding\n- **Simple API**: Easy-to-use `encode({ obj: )` and `decode({ notation: } })` functions\n- **JSON/Lino Conversion**: Convert between JSON and Links Notation with `jsonToLino({ json: )` and `linoToJson({ lino: } })`\n- **Reference Escaping**: Properly escape strings for Links Notation format with `escapeReference({ value: )`\n- **Fuzzy Matching**: Find similar strings with Levenshtein distance and keyword similarity\n\n## Installation\n\n```bash\nnpm install lino-objects-codec\n```\n\nOr with other package managers:\n\n```bash\n# Bun\nbun add lino-objects-codec\n\n# Yarn\nyarn add lino-objects-codec\n\n# pnpm\npnpm add lino-objects-codec\n```\n\n## Quick Start\n\n```javascript\nimport { encode, decode } from 'lino-objects-codec';\n\n// Encode basic types\nconst encoded = encode({ obj: { name: 'Alice', age: 30, active: true } } } });\nconsole.log(encoded);\n// Output: (object obj_0 ((str bmFt...) (int 30)) ((str YWN0...) (bool true)))\n\n// Decode back to JavaScript object\nconst decoded = decode({ notation: encoded } });\nconsole.log(decoded);\n// Output: { name: 'Alice', age: 30, active: true }\n\n// Roundtrip preserves data\nconsole.log(JSON.stringify(decoded) === JSON.stringify({ name: 'Alice', age: 30, active: true }));\n// Output: true\n```\n\n## Usage Examples\n\n### Basic Types\n\n```javascript\nimport { encode, decode } from 'lino-objects-codec';\n\n// null and undefined\nconsole.log(decode({ notation: encode({ obj: null } }))); // null\nconsole.log(decode({ notation: encode({ obj: undefined } }))); // undefined\n\n// Booleans\nconsole.log(decode({ notation: encode({ obj: true } }))); // true\nconsole.log(decode({ notation: encode({ obj: false } }))); // false\n\n// Numbers (integers and floats)\nconsole.log(decode({ notation: encode({ obj: 42 } }))); // 42\nconsole.log(decode({ notation: encode({ obj: -123 } }))); // -123\nconsole.log(decode({ notation: encode({ obj: 3.14 } }))); // 3.14\n\n// Special number values\nconsole.log(decode({ notation: encode({ obj: Infinity } }))); // Infinity\nconsole.log(decode({ notation: encode({ obj: -Infinity } }))); // -Infinity\nconsole.log(Number.isNaN(decode({ notation: encode({ obj: NaN } })))); // true\n\n// Strings (with full Unicode support)\nconsole.log(decode({ notation: encode({ obj: 'hello' } }))); // 'hello'\nconsole.log(decode({ notation: encode({ obj: '你好世界 🌍' } }))); // '你好世界 🌍'\nconsole.log(decode({ notation: encode({ obj: 'multi\\nline\\nstring' } }))); // 'multi\\nline\\nstring'\n```\n\n### Collections\n\n```javascript\nimport { encode, decode } from 'lino-objects-codec';\n\n// Arrays\nconst data = [1, 2, 3, 'hello', true, null];\nconsole.log(JSON.stringify(decode({ notation: encode({ obj: data } }))) === JSON.stringify(data)); // true\n\n// Nested arrays\nconst nested = [[1, 2], [3, 4], [5, [6, 7]]];\nconsole.log(JSON.stringify(decode({ notation: encode({ obj: nested } }))) === JSON.stringify(nested)); // true\n\n// Objects\nconst person = {\n name: 'Bob',\n age: 25,\n email: 'bob@example.com',\n};\nconsole.log(JSON.stringify(decode({ notation: encode({ obj: person } }))) === JSON.stringify(person)); // true\n\n// Complex nested structures\nconst complexData = {\n users: [\n { id: 1, name: 'Alice' },\n { id: 2, name: 'Bob' },\n ],\n metadata: {\n version: 1,\n count: 2,\n },\n};\nconsole.log(JSON.stringify(decode({ notation: encode({ obj: complexData } }))) === JSON.stringify(complexData)); // true\n```\n\n### Circular References\n\nThe library automatically handles circular references and shared objects:\n\n```javascript\nimport { encode, decode } from 'lino-objects-codec';\n\n// Self-referencing array\nconst arr = [1, 2, 3];\narr.push(arr); // Circular reference\nconst encoded = encode({ obj: arr });\nconst decoded = decode({ notation: encoded });\nconsole.log(decoded[3] === decoded); // true - Reference preserved\n\n// Self-referencing object\nconst obj = { name: 'root' };\nobj.self = obj; // Circular reference\nconst encoded2 = encode({ obj: obj });\nconst decoded2 = decode({ notation: encoded2 });\nconsole.log(decoded2.self === decoded2); // true - Reference preserved\n\n// Shared references\nconst shared = { shared: 'data' };\nconst container = { first: shared, second: shared };\nconst encoded3 = encode({ obj: container });\nconst decoded3 = decode({ notation: encoded3 });\n// Both references point to the same object\nconsole.log(decoded3.first === decoded3.second); // true\n\n// Complex circular structure (tree with back-references)\nconst root = { name: 'root', children: [] };\nconst child = { name: 'child', parent: root };\nroot.children.push(child);\nconst encoded4 = encode({ obj: root });\nconst decoded4 = decode({ notation: encoded4 });\nconsole.log(decoded4.children[0].parent === decoded4); // true\n```\n\n### JSON/Lino Conversion\n\nConvert between JSON and Links Notation format:\n\n```javascript\nimport { jsonToLino, linoToJson, escapeReference } from 'lino-objects-codec';\n\n// Convert JSON to Links Notation\nconst data = { name: 'Alice', age: 30 };\nconst lino = jsonToLino({ json: data });\nconsole.log(lino);\n// Output: ((name Alice) (age 30))\n\n// Convert Links Notation back to JSON\nconst json = linoToJson({ lino: '((name Alice }) (age 30))');\nconsole.log(json);\n// Output: { name: 'Alice', age: 30 }\n\n// Escape strings for Links Notation\nconsole.log(escapeReference({ value: 'hello' })); // hello\nconsole.log(escapeReference({ value: 'hello world' })); // 'hello world'\nconsole.log(escapeReference({ value: \"it's\" })); // \"it's\"\nconsole.log(escapeReference({ value: 'key:value' })); // \"key:value\"\n```\n\n### Fuzzy Matching\n\nFind similar strings using edit distance and keyword similarity:\n\n```javascript\nimport {\n levenshteinDistance,\n stringSimilarity,\n findBestMatch,\n findAllMatches,\n extractKeywords,\n normalizeQuestion,\n} from 'lino-objects-codec';\n\n// Calculate edit distance\nconst distance = levenshteinDistance({ a: 'hello', b: 'hallo' }); // 1\n\n// Calculate similarity (0-1)\nconst similarity = stringSimilarity({ a: 'hello', b: 'hallo' }); // 0.8\n\n// Normalize questions for comparison\nconst normalized = normalizeQuestion({ question: 'What is your NAME?' });\n// Output: 'what is your name'\n\n// Extract keywords (no stopwords by default)\nconst keywords = extractKeywords({ question: 'What is the best programming language?' });\n// Output: Set { 'what', 'is', 'the', 'best', 'programming', 'language', 'progr' }\n\n// Extract keywords with custom stopwords\nconst stopwords = new Set(['what', 'is', 'the']);\nconst filteredKeywords = extractKeywords({ question: 'What is the best programming language?', stopwords });\n// Output: Set { 'best', 'programming', 'language', 'progr' }\n\n// Find best matching question in a database\nconst qaDatabase = new Map([\n ['What is your name?', 'Claude'],\n ['How old are you?', 'Unknown'],\n]);\n\nconst match = findBestMatch({ question: { question: 'What is your age?', qaDatabase: qaDatabase: qaDatabase, threshold: 0.3 } });\n// Returns: { question: 'How old are you?', answer: 'Unknown', score: 0.xx }\n\n// Find all matches above threshold\nconst matches = findAllMatches({ question: { question: 'What is your name?', qaDatabase: qaDatabase: qaDatabase, threshold: 0.3 } });\n```\n\n## How It Works\n\nThe library uses the [links-notation](https://github.com/link-foundation/links-notation) format as the serialization target. Each JavaScript object is encoded as a Link with type information:\n\n- Basic types are encoded with type markers: `(int 42)`, `(str \"hello\")`, `(bool true)`\n- Strings are base64-encoded to handle special characters and newlines\n- Collections include object IDs for reference tracking: `(array obj_0 item1 item2 ...)`\n- Circular references use special `ref` links: `(ref obj_0)`\n\nThis approach allows for:\n\n- Universal representation of object graphs\n- Preservation of object identity\n- Natural handling of circular references\n- Human-readable (somewhat) output\n\n## API Reference\n\n### Typed Object Codec\n\n#### `encode({ obj: obj })`\n\nEncode a JavaScript object to Links Notation format with type markers.\n\n**Parameters:**\n\n- `options.obj` - The JavaScript object to encode\n\n**Returns:**\n\n- String representation in Links Notation format\n\n**Throws:**\n\n- `TypeError` - If the object type is not supported\n\n#### `decode({ notation: notation })`\n\nDecode Links Notation format to a JavaScript object.\n\n**Parameters:**\n\n- `options.notation` - String in Links Notation format\n\n**Returns:**\n\n- Reconstructed JavaScript object\n\n#### `ObjectCodec`\n\nThe main codec class that performs encoding and decoding. The module-level `encode({ obj: )` and `decode({ notation: } })` functions use a shared instance of this class.\n\n```javascript\nimport { ObjectCodec } from 'lino-objects-codec';\n\nconst codec = new ObjectCodec();\nconst encoded = codec.encode({ data: [1, 2, 3] });\nconst decoded = codec.decode({ notation: encoded });\n```\n\n### JSON/Lino Conversion\n\n#### `jsonToLino({ json: json })`\n\nConvert JSON data to Links Notation format.\n\n**Parameters:**\n\n- `options.json` - Any JSON-serializable value (object, array, string, number, boolean, null)\n\n**Returns:**\n\n- Links Notation string representation\n\n```javascript\njsonToLino({ name: 'Alice', age: 30 });\n// Returns: ((name Alice) (age 30))\n\njsonToLino({ json: [1, 2, 3] });\n// Returns: (1 2 3)\n```\n\n#### `linoToJson({ lino: lino })`\n\nConvert Links Notation to JSON.\n\n**Parameters:**\n\n- `options.lino` - Links Notation string\n\n**Returns:**\n\n- Parsed JSON value\n\n```javascript\nlinoToJson({ lino: '((name Alice }) (age 30))');\n// Returns: { name: 'Alice', age: 30 }\n```\n\n#### `escapeReference({ value: value })`\n\nEscape a value for safe use in Links Notation format.\n\n**Parameters:**\n\n- `options.value` - The value to escape (string, number, or boolean)\n\n**Returns:**\n\n- Escaped string suitable for Links Notation\n\n```javascript\nescapeReference({ value: 'hello' }); // 'hello'\nescapeReference({ value: 'hello world' }); // \"'hello world'\"\nescapeReference({ value: \"it's\" }); // \"\\\"it's\\\"\"\n```\n\n#### `unescapeReference(options = {})`\n\nUnescape a Links Notation reference.\n\n**Parameters:**\n\n- `options.str` - The escaped reference string\n\n**Returns:**\n\n- Unescaped string\n\n#### `formatAsLino(options = {})`\n\nFormat an array as Links Notation with proper indentation.\n\n**Parameters:**\n\n- `options.values` - Array of values\n\n**Returns:**\n\n- Formatted Links Notation string\n\n### Fuzzy Matching Utilities\n\n#### `levenshteinDistance(options = {})`\n\nCalculate edit distance between two strings.\n\n**Parameters:**\n\n- `options.a`, `options.b` - Strings to compare\n\n**Returns:**\n\n- Number of edits (insertions, deletions, substitutions) needed\n\n#### `stringSimilarity(options = {})`\n\nCalculate normalized similarity score between two strings.\n\n**Parameters:**\n\n- `options.a`, `options.b` - Strings to compare\n\n**Returns:**\n\n- Score between 0 (completely different) and 1 (identical)\n\n#### `normalizeQuestion({ question: question })`\n\nNormalize a question for comparison (lowercase, remove punctuation, standardize whitespace).\n\n**Parameters:**\n\n- `options.question` - Question string\n\n**Returns:**\n\n- Normalized string\n\n#### `extractKeywords(options = {})`\n\nExtract meaningful keywords from a question, optionally filtering out stopwords.\n\n**Parameters:**\n\n- `options.question` - Question string\n- `options.stopwords` - Custom stopwords set to filter out (default: empty Set, no filtering)\n- `options.minWordLength` - Minimum word length (default: 2)\n- `options.stemLength` - Length for word stemming (default: 5, 0 to disable)\n\n**Returns:**\n\n- Set of keywords\n\n#### `keywordSimilarity(options = {})`\n\nCalculate keyword overlap similarity (Jaccard index).\n\n**Parameters:**\n\n- `options.a`, `options.b` - Questions to compare\n- `options` - Same as extractKeywords\n\n**Returns:**\n\n- Score between 0 and 1\n\n#### `findBestMatch({ question: question, qaDatabase: database, options })`\n\nFind the best matching question from a database.\n\n**Parameters:**\n\n- `options.question` - Question to match\n- `options.qaDatabase` - Map of questions to answers\n- `options.threshold` - Minimum similarity threshold (default: 0.4)\n- `options.editWeight` - Weight for edit distance similarity (default: 0.4)\n- `options.keywordWeight` - Weight for keyword similarity (default: 0.6)\n- `options.stopwords` - Stopwords to filter from keyword extraction\n- `options.minWordLength` - Minimum word length for keyword extraction\n- `options.stemLength` - Stem length for keyword extraction\n\n**Returns:**\n\n- `{ question, answer, score }` or null if no match above threshold\n\n#### `findAllMatches({ question: question, qaDatabase: database, options })`\n\nFind all matches above a threshold, sorted by score.\n\n**Parameters:**\n\n- Same as findBestMatch\n\n**Returns:**\n\n- Array of `{ question, answer, score }` sorted by score descending\n\n## Development\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/link-foundation/lino-objects-codec.git\ncd lino-objects-codec/js\n\n# Install dependencies\nnpm install\n```\n\n### Running Tests\n\n```bash\n# Run all tests\nnpm test\n\n# Run example\nnpm run example\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Add tests for your changes\n4. Ensure all tests pass (`npm test`)\n5. Commit your changes (`git commit -m 'Add amazing feature'`)\n6. Push to the branch (`git push origin feature/amazing-feature`)\n7. Open a Pull Request\n\n## License\n\nThis project is licensed under the Unlicense - see the [LICENSE](../LICENSE) file for details.\n\n## Links\n\n- [GitHub Repository](https://github.com/link-foundation/lino-objects-codec)\n- [Links Notation Specification](https://github.com/link-foundation/links-notation)\n- [npm Package](https://www.npmjs.com/package/lino-objects-codec/)\n- [Python Implementation](../python/)\n\n## Acknowledgments\n\nThis project is built on top of the [links-notation](https://github.com/link-foundation/links-notation) library.\n","readmeFilename":"README.md"} \ No newline at end of file diff --git a/docs/case-studies/issue-25/registry-nuget-not-found.txt b/docs/case-studies/issue-25/registry-nuget-not-found.txt new file mode 100644 index 0000000..b94614d --- /dev/null +++ b/docs/case-studies/issue-25/registry-nuget-not-found.txt @@ -0,0 +1,3 @@ +BlobNotFoundThe specified blob does not exist. +RequestId:6f050ce1-501e-000f-1def-dacb9b000000 +Time:2026-05-03T11:22:04.7965492Z \ No newline at end of file diff --git a/docs/case-studies/issue-25/registry-presence.sh b/docs/case-studies/issue-25/registry-presence.sh new file mode 100755 index 0000000..10da023 --- /dev/null +++ b/docs/case-studies/issue-25/registry-presence.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Reproduces the registry presence check for lino-objects-codec. +# Re-run this any time to re-validate the publication state. +set -u +echo "npm: $(curl -sS -o /dev/null -w '%{http_code}' https://registry.npmjs.org/lino-objects-codec)" +echo "PyPI: $(curl -sS -o /dev/null -w '%{http_code}' https://pypi.org/pypi/lino-objects-codec/json)" +echo "crates: $(curl -sS -o /dev/null -w '%{http_code}' https://crates.io/api/v1/crates/lino-objects-codec)" +echo "NuGet: $(curl -sS -o /dev/null -w '%{http_code}' https://api.nuget.org/v3-flatcontainer/lino.objects.codec/index.json)" diff --git a/docs/case-studies/issue-25/registry-pypi-not-found.txt b/docs/case-studies/issue-25/registry-pypi-not-found.txt new file mode 100644 index 0000000..65e6ad8 --- /dev/null +++ b/docs/case-studies/issue-25/registry-pypi-not-found.txt @@ -0,0 +1 @@ +{"message": "Not Found"} \ No newline at end of file diff --git a/docs/case-studies/issue-25/run-20804476989.json b/docs/case-studies/issue-25/run-20804476989.json new file mode 100644 index 0000000..92f606b --- /dev/null +++ b/docs/case-studies/issue-25/run-20804476989.json @@ -0,0 +1 @@ +{"conclusion":"failure","createdAt":"2026-01-08T03:29:00Z","databaseId":20804476989,"event":"push","headBranch":"main","headSha":"5ecc9f59ff628f2b8af445f1e79b1faeed2cf9a3","jobs":[{"completedAt":"2026-01-08T03:29:20Z","conclusion":"success","databaseId":59755772480,"name":"Lint and Format Check","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Setup Python","number":3,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:17Z","conclusion":"success","name":"Install dependencies","number":4,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:17Z","conclusion":"success","name":"Run Ruff linting","number":5,"startedAt":"2026-01-08T03:29:17Z","status":"completed"},{"completedAt":"2026-01-08T03:29:17Z","conclusion":"success","name":"Check Ruff formatting","number":6,"startedAt":"2026-01-08T03:29:17Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Run mypy","number":7,"startedAt":"2026-01-08T03:29:17Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Check file size limit","number":8,"startedAt":"2026-01-08T03:29:18Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Post Setup Python","number":15,"startedAt":"2026-01-08T03:29:18Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T03:29:18Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T03:29:18Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804476989/job/59755772480"},{"completedAt":"2026-01-08T03:29:25Z","conclusion":"success","databaseId":59755772493,"name":"Test (Python 3.13)","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Setup Python","number":3,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Install dependencies","number":4,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:19Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T03:29:18Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Upload coverage to Codecov","number":6,"startedAt":"2026-01-08T03:29:19Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Post Setup Python","number":11,"startedAt":"2026-01-08T03:29:23Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:29:23Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:29:23Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804476989/job/59755772493"},{"completedAt":"2026-01-08T03:29:00Z","conclusion":"skipped","databaseId":59755772610,"name":"Changelog Fragment Check","startedAt":"2026-01-08T03:29:01Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804476989/job/59755772610"},{"completedAt":"2026-01-08T03:29:43Z","conclusion":"success","databaseId":59755794151,"name":"Build Package","startedAt":"2026-01-08T03:29:27Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:29Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:28Z","status":"completed"},{"completedAt":"2026-01-08T03:29:30Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:29Z","status":"completed"},{"completedAt":"2026-01-08T03:29:30Z","conclusion":"success","name":"Setup Python","number":3,"startedAt":"2026-01-08T03:29:30Z","status":"completed"},{"completedAt":"2026-01-08T03:29:36Z","conclusion":"success","name":"Install build dependencies","number":4,"startedAt":"2026-01-08T03:29:30Z","status":"completed"},{"completedAt":"2026-01-08T03:29:40Z","conclusion":"success","name":"Build package","number":5,"startedAt":"2026-01-08T03:29:36Z","status":"completed"},{"completedAt":"2026-01-08T03:29:40Z","conclusion":"success","name":"Check package","number":6,"startedAt":"2026-01-08T03:29:40Z","status":"completed"},{"completedAt":"2026-01-08T03:29:41Z","conclusion":"success","name":"Upload artifacts","number":7,"startedAt":"2026-01-08T03:29:40Z","status":"completed"},{"completedAt":"2026-01-08T03:29:41Z","conclusion":"success","name":"Post Setup Python","number":13,"startedAt":"2026-01-08T03:29:41Z","status":"completed"},{"completedAt":"2026-01-08T03:29:41Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":14,"startedAt":"2026-01-08T03:29:41Z","status":"completed"},{"completedAt":"2026-01-08T03:29:41Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-01-08T03:29:41Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804476989/job/59755794151"},{"completedAt":"2026-01-08T03:30:06Z","conclusion":"failure","databaseId":59755809066,"name":"Auto Release","startedAt":"2026-01-08T03:29:45Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:48Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:46Z","status":"completed"},{"completedAt":"2026-01-08T03:29:49Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:48Z","status":"completed"},{"completedAt":"2026-01-08T03:29:49Z","conclusion":"success","name":"Setup Python","number":3,"startedAt":"2026-01-08T03:29:49Z","status":"completed"},{"completedAt":"2026-01-08T03:29:57Z","conclusion":"success","name":"Install dependencies","number":4,"startedAt":"2026-01-08T03:29:49Z","status":"completed"},{"completedAt":"2026-01-08T03:29:57Z","conclusion":"success","name":"Check if version changed","number":5,"startedAt":"2026-01-08T03:29:57Z","status":"completed"},{"completedAt":"2026-01-08T03:29:58Z","conclusion":"success","name":"Download artifacts","number":6,"startedAt":"2026-01-08T03:29:57Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"failure","name":"Publish to PyPI","number":7,"startedAt":"2026-01-08T03:29:58Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"skipped","name":"Create GitHub Release","number":8,"startedAt":"2026-01-08T03:30:04Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"success","name":"Post Publish to PyPI","number":14,"startedAt":"2026-01-08T03:30:04Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"skipped","name":"Post Setup Python","number":15,"startedAt":"2026-01-08T03:30:04Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T03:30:04Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T03:30:04Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804476989/job/59755809066"},{"completedAt":"2026-01-08T03:29:43Z","conclusion":"skipped","databaseId":59755809186,"name":"Manual Release","startedAt":"2026-01-08T03:29:43Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804476989/job/59755809186"}],"name":"Python CI/CD","status":"completed","workflowName":"Python CI/CD"} diff --git a/docs/case-studies/issue-25/run-20804477002.json b/docs/case-studies/issue-25/run-20804477002.json new file mode 100644 index 0000000..0cfdc99 --- /dev/null +++ b/docs/case-studies/issue-25/run-20804477002.json @@ -0,0 +1 @@ +{"conclusion":"failure","createdAt":"2026-01-08T03:29:00Z","databaseId":20804477002,"event":"push","headBranch":"main","headSha":"5ecc9f59ff628f2b8af445f1e79b1faeed2cf9a3","jobs":[{"completedAt":"2026-01-08T03:29:25Z","conclusion":"success","databaseId":59755772450,"name":"Test (Rust on ubuntu-latest)","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:07Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:07Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T03:29:07Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T03:29:07Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Run example","number":6,"startedAt":"2026-01-08T03:29:23Z","status":"completed"},{"completedAt":"2026-01-08T03:29:24Z","conclusion":"success","name":"Post Cache cargo dependencies","number":11,"startedAt":"2026-01-08T03:29:23Z","status":"completed"},{"completedAt":"2026-01-08T03:29:24Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:29:24Z","status":"completed"},{"completedAt":"2026-01-08T03:29:24Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:29:24Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755772450"},{"completedAt":"2026-01-08T03:29:17Z","conclusion":"success","databaseId":59755772454,"name":"Lint and Format Check","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:06Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:07Z","conclusion":"success","name":"Check formatting","number":5,"startedAt":"2026-01-08T03:29:06Z","status":"completed"},{"completedAt":"2026-01-08T03:29:14Z","conclusion":"success","name":"Run clippy","number":6,"startedAt":"2026-01-08T03:29:07Z","status":"completed"},{"completedAt":"2026-01-08T03:29:15Z","conclusion":"success","name":"Post Cache cargo dependencies","number":11,"startedAt":"2026-01-08T03:29:14Z","status":"completed"},{"completedAt":"2026-01-08T03:29:15Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:29:15Z","status":"completed"},{"completedAt":"2026-01-08T03:29:15Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:29:15Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755772454"},{"completedAt":"2026-01-08T03:30:13Z","conclusion":"success","databaseId":59755772457,"name":"Test (Rust on windows-latest)","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:09Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:21Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T03:29:09Z","status":"completed"},{"completedAt":"2026-01-08T03:29:21Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T03:29:21Z","status":"completed"},{"completedAt":"2026-01-08T03:29:50Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T03:29:21Z","status":"completed"},{"completedAt":"2026-01-08T03:29:50Z","conclusion":"success","name":"Run example","number":6,"startedAt":"2026-01-08T03:29:50Z","status":"completed"},{"completedAt":"2026-01-08T03:30:10Z","conclusion":"success","name":"Post Cache cargo dependencies","number":11,"startedAt":"2026-01-08T03:29:50Z","status":"completed"},{"completedAt":"2026-01-08T03:30:12Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:30:10Z","status":"completed"},{"completedAt":"2026-01-08T03:30:12Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:30:12Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755772457"},{"completedAt":"2026-01-08T03:29:21Z","conclusion":"success","databaseId":59755772487,"name":"Test (Rust on macos-latest)","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:02Z","status":"completed"},{"completedAt":"2026-01-08T03:29:06Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:07Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T03:29:06Z","status":"completed"},{"completedAt":"2026-01-08T03:29:07Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T03:29:07Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T03:29:07Z","status":"completed"},{"completedAt":"2026-01-08T03:29:18Z","conclusion":"success","name":"Run example","number":6,"startedAt":"2026-01-08T03:29:18Z","status":"completed"},{"completedAt":"2026-01-08T03:29:19Z","conclusion":"success","name":"Post Cache cargo dependencies","number":11,"startedAt":"2026-01-08T03:29:18Z","status":"completed"},{"completedAt":"2026-01-08T03:29:20Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:29:19Z","status":"completed"},{"completedAt":"2026-01-08T03:29:20Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:29:20Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755772487"},{"completedAt":"2026-01-08T03:29:00Z","conclusion":"skipped","databaseId":59755772509,"name":"Changelog Fragment Check","startedAt":"2026-01-08T03:29:00Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755772509"},{"completedAt":"2026-01-08T03:30:30Z","conclusion":"success","databaseId":59755834754,"name":"Build Package","startedAt":"2026-01-08T03:30:15Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:30:17Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:30:16Z","status":"completed"},{"completedAt":"2026-01-08T03:30:18Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:30:17Z","status":"completed"},{"completedAt":"2026-01-08T03:30:19Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T03:30:18Z","status":"completed"},{"completedAt":"2026-01-08T03:30:20Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T03:30:19Z","status":"completed"},{"completedAt":"2026-01-08T03:30:24Z","conclusion":"success","name":"Build release","number":5,"startedAt":"2026-01-08T03:30:20Z","status":"completed"},{"completedAt":"2026-01-08T03:30:27Z","conclusion":"success","name":"Package crate","number":6,"startedAt":"2026-01-08T03:30:24Z","status":"completed"},{"completedAt":"2026-01-08T03:30:27Z","conclusion":"success","name":"Post Cache cargo dependencies","number":11,"startedAt":"2026-01-08T03:30:27Z","status":"completed"},{"completedAt":"2026-01-08T03:30:28Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:30:27Z","status":"completed"},{"completedAt":"2026-01-08T03:30:28Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:30:28Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755834754"},{"completedAt":"2026-01-08T03:30:45Z","conclusion":"failure","databaseId":59755852320,"name":"Auto Release","startedAt":"2026-01-08T03:30:32Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:30:34Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:30:33Z","status":"completed"},{"completedAt":"2026-01-08T03:30:35Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:30:34Z","status":"completed"},{"completedAt":"2026-01-08T03:30:36Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T03:30:35Z","status":"completed"},{"completedAt":"2026-01-08T03:30:39Z","conclusion":"success","name":"Setup Node.js","number":4,"startedAt":"2026-01-08T03:30:36Z","status":"completed"},{"completedAt":"2026-01-08T03:30:39Z","conclusion":"success","name":"Check if version changed","number":5,"startedAt":"2026-01-08T03:30:39Z","status":"completed"},{"completedAt":"2026-01-08T03:30:39Z","conclusion":"success","name":"Publish to crates.io","number":6,"startedAt":"2026-01-08T03:30:39Z","status":"completed"},{"completedAt":"2026-01-08T03:30:42Z","conclusion":"failure","name":"Create GitHub Release","number":7,"startedAt":"2026-01-08T03:30:39Z","status":"completed"},{"completedAt":"2026-01-08T03:30:42Z","conclusion":"skipped","name":"Post Setup Node.js","number":13,"startedAt":"2026-01-08T03:30:42Z","status":"completed"},{"completedAt":"2026-01-08T03:30:43Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":14,"startedAt":"2026-01-08T03:30:42Z","status":"completed"},{"completedAt":"2026-01-08T03:30:43Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-01-08T03:30:43Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755852320"},{"completedAt":"2026-01-08T03:30:30Z","conclusion":"skipped","databaseId":59755852475,"name":"Manual Release","startedAt":"2026-01-08T03:30:30Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477002/job/59755852475"}],"name":"Rust CI/CD","status":"completed","workflowName":"Rust CI/CD"} diff --git a/docs/case-studies/issue-25/run-20804477007.json b/docs/case-studies/issue-25/run-20804477007.json new file mode 100644 index 0000000..4e458b8 --- /dev/null +++ b/docs/case-studies/issue-25/run-20804477007.json @@ -0,0 +1 @@ +{"conclusion":"failure","createdAt":"2026-01-08T03:29:00Z","databaseId":20804477007,"event":"push","headBranch":"main","headSha":"5ecc9f59ff628f2b8af445f1e79b1faeed2cf9a3","jobs":[{"completedAt":"2026-01-08T03:29:32Z","conclusion":"success","databaseId":59755772448,"name":"Lint and Format Check","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:06Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:06Z","conclusion":"success","name":"Setup .NET","number":3,"startedAt":"2026-01-08T03:29:06Z","status":"completed"},{"completedAt":"2026-01-08T03:29:15Z","conclusion":"success","name":"Restore dependencies","number":4,"startedAt":"2026-01-08T03:29:06Z","status":"completed"},{"completedAt":"2026-01-08T03:29:24Z","conclusion":"success","name":"Check formatting","number":5,"startedAt":"2026-01-08T03:29:15Z","status":"completed"},{"completedAt":"2026-01-08T03:29:30Z","conclusion":"success","name":"Build with warnings as errors","number":6,"startedAt":"2026-01-08T03:29:24Z","status":"completed"},{"completedAt":"2026-01-08T03:29:30Z","conclusion":"success","name":"Post Setup .NET","number":11,"startedAt":"2026-01-08T03:29:30Z","status":"completed"},{"completedAt":"2026-01-08T03:29:30Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T03:29:30Z","status":"completed"},{"completedAt":"2026-01-08T03:29:30Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T03:29:30Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755772448"},{"completedAt":"2026-01-08T03:29:39Z","conclusion":"success","databaseId":59755772463,"name":"Test (.NET on ubuntu-latest)","startedAt":"2026-01-08T03:29:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:03Z","status":"completed"},{"completedAt":"2026-01-08T03:29:04Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:05Z","conclusion":"success","name":"Setup .NET","number":3,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:14Z","conclusion":"success","name":"Restore dependencies","number":4,"startedAt":"2026-01-08T03:29:05Z","status":"completed"},{"completedAt":"2026-01-08T03:29:25Z","conclusion":"success","name":"Build","number":5,"startedAt":"2026-01-08T03:29:14Z","status":"completed"},{"completedAt":"2026-01-08T03:29:32Z","conclusion":"success","name":"Run tests","number":6,"startedAt":"2026-01-08T03:29:25Z","status":"completed"},{"completedAt":"2026-01-08T03:29:34Z","conclusion":"success","name":"Run example","number":7,"startedAt":"2026-01-08T03:29:32Z","status":"completed"},{"completedAt":"2026-01-08T03:29:37Z","conclusion":"success","name":"Upload coverage (Ubuntu only)","number":8,"startedAt":"2026-01-08T03:29:34Z","status":"completed"},{"completedAt":"2026-01-08T03:29:37Z","conclusion":"success","name":"Post Setup .NET","number":15,"startedAt":"2026-01-08T03:29:37Z","status":"completed"},{"completedAt":"2026-01-08T03:29:38Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T03:29:37Z","status":"completed"},{"completedAt":"2026-01-08T03:29:38Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T03:29:38Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755772463"},{"completedAt":"2026-01-08T03:29:28Z","conclusion":"success","databaseId":59755772464,"name":"Test (.NET on macos-latest)","startedAt":"2026-01-08T03:29:03Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:07Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:09Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:07Z","status":"completed"},{"completedAt":"2026-01-08T03:29:11Z","conclusion":"success","name":"Setup .NET","number":3,"startedAt":"2026-01-08T03:29:09Z","status":"completed"},{"completedAt":"2026-01-08T03:29:15Z","conclusion":"success","name":"Restore dependencies","number":4,"startedAt":"2026-01-08T03:29:11Z","status":"completed"},{"completedAt":"2026-01-08T03:29:20Z","conclusion":"success","name":"Build","number":5,"startedAt":"2026-01-08T03:29:15Z","status":"completed"},{"completedAt":"2026-01-08T03:29:23Z","conclusion":"success","name":"Run tests","number":6,"startedAt":"2026-01-08T03:29:20Z","status":"completed"},{"completedAt":"2026-01-08T03:29:25Z","conclusion":"success","name":"Run example","number":7,"startedAt":"2026-01-08T03:29:23Z","status":"completed"},{"completedAt":"2026-01-08T03:29:25Z","conclusion":"skipped","name":"Upload coverage (Ubuntu only)","number":8,"startedAt":"2026-01-08T03:29:25Z","status":"completed"},{"completedAt":"2026-01-08T03:29:25Z","conclusion":"success","name":"Post Setup .NET","number":15,"startedAt":"2026-01-08T03:29:25Z","status":"completed"},{"completedAt":"2026-01-08T03:29:26Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T03:29:25Z","status":"completed"},{"completedAt":"2026-01-08T03:29:26Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T03:29:26Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755772464"},{"completedAt":"2026-01-08T03:29:59Z","conclusion":"success","databaseId":59755772475,"name":"Test (.NET on windows-latest)","startedAt":"2026-01-08T03:29:03Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:29:06Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:29:04Z","status":"completed"},{"completedAt":"2026-01-08T03:29:11Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:29:06Z","status":"completed"},{"completedAt":"2026-01-08T03:29:21Z","conclusion":"success","name":"Setup .NET","number":3,"startedAt":"2026-01-08T03:29:11Z","status":"completed"},{"completedAt":"2026-01-08T03:29:37Z","conclusion":"success","name":"Restore dependencies","number":4,"startedAt":"2026-01-08T03:29:21Z","status":"completed"},{"completedAt":"2026-01-08T03:29:45Z","conclusion":"success","name":"Build","number":5,"startedAt":"2026-01-08T03:29:37Z","status":"completed"},{"completedAt":"2026-01-08T03:29:52Z","conclusion":"success","name":"Run tests","number":6,"startedAt":"2026-01-08T03:29:45Z","status":"completed"},{"completedAt":"2026-01-08T03:29:55Z","conclusion":"success","name":"Run example","number":7,"startedAt":"2026-01-08T03:29:52Z","status":"completed"},{"completedAt":"2026-01-08T03:29:55Z","conclusion":"skipped","name":"Upload coverage (Ubuntu only)","number":8,"startedAt":"2026-01-08T03:29:55Z","status":"completed"},{"completedAt":"2026-01-08T03:29:55Z","conclusion":"success","name":"Post Setup .NET","number":15,"startedAt":"2026-01-08T03:29:55Z","status":"completed"},{"completedAt":"2026-01-08T03:29:57Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T03:29:55Z","status":"completed"},{"completedAt":"2026-01-08T03:29:57Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T03:29:57Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755772475"},{"completedAt":"2026-01-08T03:29:00Z","conclusion":"skipped","databaseId":59755772615,"name":"Changeset Check","startedAt":"2026-01-08T03:29:01Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755772615"},{"completedAt":"2026-01-08T03:30:22Z","conclusion":"success","databaseId":59755822393,"name":"Build Package","startedAt":"2026-01-08T03:30:02Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:30:03Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:30:02Z","status":"completed"},{"completedAt":"2026-01-08T03:30:04Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:30:03Z","status":"completed"},{"completedAt":"2026-01-08T03:30:05Z","conclusion":"success","name":"Setup .NET","number":3,"startedAt":"2026-01-08T03:30:04Z","status":"completed"},{"completedAt":"2026-01-08T03:30:11Z","conclusion":"success","name":"Restore dependencies","number":4,"startedAt":"2026-01-08T03:30:05Z","status":"completed"},{"completedAt":"2026-01-08T03:30:18Z","conclusion":"success","name":"Build","number":5,"startedAt":"2026-01-08T03:30:11Z","status":"completed"},{"completedAt":"2026-01-08T03:30:19Z","conclusion":"success","name":"Pack NuGet package","number":6,"startedAt":"2026-01-08T03:30:18Z","status":"completed"},{"completedAt":"2026-01-08T03:30:20Z","conclusion":"success","name":"Upload artifacts","number":7,"startedAt":"2026-01-08T03:30:19Z","status":"completed"},{"completedAt":"2026-01-08T03:30:20Z","conclusion":"success","name":"Post Setup .NET","number":13,"startedAt":"2026-01-08T03:30:20Z","status":"completed"},{"completedAt":"2026-01-08T03:30:20Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":14,"startedAt":"2026-01-08T03:30:20Z","status":"completed"},{"completedAt":"2026-01-08T03:30:20Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-01-08T03:30:20Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755822393"},{"completedAt":"2026-01-08T03:30:36Z","conclusion":"failure","databaseId":59755844052,"name":"Auto Release","startedAt":"2026-01-08T03:30:24Z","status":"completed","steps":[{"completedAt":"2026-01-08T03:30:26Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T03:30:24Z","status":"completed"},{"completedAt":"2026-01-08T03:30:26Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T03:30:26Z","status":"completed"},{"completedAt":"2026-01-08T03:30:27Z","conclusion":"success","name":"Setup .NET","number":3,"startedAt":"2026-01-08T03:30:26Z","status":"completed"},{"completedAt":"2026-01-08T03:30:30Z","conclusion":"success","name":"Setup Node.js","number":4,"startedAt":"2026-01-08T03:30:27Z","status":"completed"},{"completedAt":"2026-01-08T03:30:30Z","conclusion":"success","name":"Check if version changed","number":5,"startedAt":"2026-01-08T03:30:30Z","status":"completed"},{"completedAt":"2026-01-08T03:30:31Z","conclusion":"success","name":"Download artifacts","number":6,"startedAt":"2026-01-08T03:30:30Z","status":"completed"},{"completedAt":"2026-01-08T03:30:31Z","conclusion":"success","name":"Publish to NuGet","number":7,"startedAt":"2026-01-08T03:30:31Z","status":"completed"},{"completedAt":"2026-01-08T03:30:35Z","conclusion":"failure","name":"Create GitHub Release","number":8,"startedAt":"2026-01-08T03:30:31Z","status":"completed"},{"completedAt":"2026-01-08T03:30:35Z","conclusion":"skipped","name":"Post Setup Node.js","number":14,"startedAt":"2026-01-08T03:30:35Z","status":"completed"},{"completedAt":"2026-01-08T03:30:35Z","conclusion":"skipped","name":"Post Setup .NET","number":15,"startedAt":"2026-01-08T03:30:35Z","status":"completed"},{"completedAt":"2026-01-08T03:30:35Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T03:30:35Z","status":"completed"},{"completedAt":"2026-01-08T03:30:35Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T03:30:35Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755844052"},{"completedAt":"2026-01-08T03:30:22Z","conclusion":"skipped","databaseId":59755844200,"name":"Manual Release","startedAt":"2026-01-08T03:30:22Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20804477007/job/59755844200"}],"name":"C# CI/CD","status":"completed","workflowName":"C# CI/CD"} diff --git a/docs/case-studies/issue-25/run-20828012412.json b/docs/case-studies/issue-25/run-20828012412.json new file mode 100644 index 0000000..7f22899 --- /dev/null +++ b/docs/case-studies/issue-25/run-20828012412.json @@ -0,0 +1 @@ +{"conclusion":"failure","createdAt":"2026-01-08T18:48:44Z","databaseId":20828012412,"event":"push","headBranch":"main","headSha":"c79c77d68a90cadbd439c9613a7050501095c92d","jobs":[{"completedAt":"2026-01-08T18:48:53Z","conclusion":"success","databaseId":59834360903,"name":"Detect Changes","startedAt":"2026-01-08T18:48:47Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:48:48Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:48:47Z","status":"completed"},{"completedAt":"2026-01-08T18:48:49Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:48:48Z","status":"completed"},{"completedAt":"2026-01-08T18:48:51Z","conclusion":"success","name":"Setup Node.js","number":3,"startedAt":"2026-01-08T18:48:49Z","status":"completed"},{"completedAt":"2026-01-08T18:48:51Z","conclusion":"success","name":"Detect changes","number":4,"startedAt":"2026-01-08T18:48:51Z","status":"completed"},{"completedAt":"2026-01-08T18:48:52Z","conclusion":"success","name":"Post Setup Node.js","number":7,"startedAt":"2026-01-08T18:48:51Z","status":"completed"},{"completedAt":"2026-01-08T18:48:52Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":8,"startedAt":"2026-01-08T18:48:52Z","status":"completed"},{"completedAt":"2026-01-08T18:48:52Z","conclusion":"success","name":"Complete job","number":9,"startedAt":"2026-01-08T18:48:52Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834360903"},{"completedAt":"2026-01-08T18:48:45Z","conclusion":"skipped","databaseId":59834361040,"name":"Create Changelog PR","startedAt":"2026-01-08T18:48:45Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834361040"},{"completedAt":"2026-01-08T18:48:45Z","conclusion":"skipped","databaseId":59834361083,"name":"Version Modification Check","startedAt":"2026-01-08T18:48:45Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834361083"},{"completedAt":"2026-01-08T18:49:08Z","conclusion":"success","databaseId":59834374204,"name":"Lint and Format Check","startedAt":"2026-01-08T18:48:55Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:48:57Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:48:56Z","status":"completed"},{"completedAt":"2026-01-08T18:48:58Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:48:57Z","status":"completed"},{"completedAt":"2026-01-08T18:48:59Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T18:48:58Z","status":"completed"},{"completedAt":"2026-01-08T18:49:00Z","conclusion":"success","name":"Setup Node.js","number":4,"startedAt":"2026-01-08T18:48:59Z","status":"completed"},{"completedAt":"2026-01-08T18:49:01Z","conclusion":"success","name":"Cache cargo dependencies","number":5,"startedAt":"2026-01-08T18:49:00Z","status":"completed"},{"completedAt":"2026-01-08T18:49:02Z","conclusion":"success","name":"Check formatting","number":6,"startedAt":"2026-01-08T18:49:01Z","status":"completed"},{"completedAt":"2026-01-08T18:49:06Z","conclusion":"success","name":"Run clippy","number":7,"startedAt":"2026-01-08T18:49:02Z","status":"completed"},{"completedAt":"2026-01-08T18:49:06Z","conclusion":"success","name":"Check file size limit","number":8,"startedAt":"2026-01-08T18:49:06Z","status":"completed"},{"completedAt":"2026-01-08T18:49:06Z","conclusion":"success","name":"Post Cache cargo dependencies","number":14,"startedAt":"2026-01-08T18:49:06Z","status":"completed"},{"completedAt":"2026-01-08T18:49:07Z","conclusion":"success","name":"Post Setup Node.js","number":15,"startedAt":"2026-01-08T18:49:06Z","status":"completed"},{"completedAt":"2026-01-08T18:49:07Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":16,"startedAt":"2026-01-08T18:49:07Z","status":"completed"},{"completedAt":"2026-01-08T18:49:07Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-01-08T18:49:07Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834374204"},{"completedAt":"2026-01-08T18:48:53Z","conclusion":"skipped","databaseId":59834374313,"name":"Changelog Fragment Check","startedAt":"2026-01-08T18:48:54Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834374313"},{"completedAt":"2026-01-08T18:49:18Z","conclusion":"success","databaseId":59834374336,"name":"Test (Rust on macos-latest)","startedAt":"2026-01-08T18:48:56Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:48:58Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:48:56Z","status":"completed"},{"completedAt":"2026-01-08T18:49:00Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:48:58Z","status":"completed"},{"completedAt":"2026-01-08T18:49:02Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T18:49:00Z","status":"completed"},{"completedAt":"2026-01-08T18:49:03Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T18:49:02Z","status":"completed"},{"completedAt":"2026-01-08T18:49:13Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T18:49:03Z","status":"completed"},{"completedAt":"2026-01-08T18:49:14Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-01-08T18:49:13Z","status":"completed"},{"completedAt":"2026-01-08T18:49:15Z","conclusion":"success","name":"Run example","number":7,"startedAt":"2026-01-08T18:49:14Z","status":"completed"},{"completedAt":"2026-01-08T18:49:15Z","conclusion":"success","name":"Post Cache cargo dependencies","number":13,"startedAt":"2026-01-08T18:49:15Z","status":"completed"},{"completedAt":"2026-01-08T18:49:16Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":14,"startedAt":"2026-01-08T18:49:15Z","status":"completed"},{"completedAt":"2026-01-08T18:49:16Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-01-08T18:49:16Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834374336"},{"completedAt":"2026-01-08T18:49:43Z","conclusion":"success","databaseId":59834374341,"name":"Test (Rust on windows-latest)","startedAt":"2026-01-08T18:48:56Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:48:58Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:48:57Z","status":"completed"},{"completedAt":"2026-01-08T18:49:03Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:48:58Z","status":"completed"},{"completedAt":"2026-01-08T18:49:09Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T18:49:03Z","status":"completed"},{"completedAt":"2026-01-08T18:49:12Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T18:49:09Z","status":"completed"},{"completedAt":"2026-01-08T18:49:37Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T18:49:12Z","status":"completed"},{"completedAt":"2026-01-08T18:49:38Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-01-08T18:49:37Z","status":"completed"},{"completedAt":"2026-01-08T18:49:39Z","conclusion":"success","name":"Run example","number":7,"startedAt":"2026-01-08T18:49:38Z","status":"completed"},{"completedAt":"2026-01-08T18:49:39Z","conclusion":"success","name":"Post Cache cargo dependencies","number":13,"startedAt":"2026-01-08T18:49:39Z","status":"completed"},{"completedAt":"2026-01-08T18:49:41Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":14,"startedAt":"2026-01-08T18:49:39Z","status":"completed"},{"completedAt":"2026-01-08T18:49:41Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-01-08T18:49:41Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834374341"},{"completedAt":"2026-01-08T18:49:07Z","conclusion":"success","databaseId":59834374349,"name":"Test (Rust on ubuntu-latest)","startedAt":"2026-01-08T18:48:56Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:48:57Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:48:56Z","status":"completed"},{"completedAt":"2026-01-08T18:48:58Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:48:57Z","status":"completed"},{"completedAt":"2026-01-08T18:48:59Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T18:48:58Z","status":"completed"},{"completedAt":"2026-01-08T18:49:00Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T18:48:59Z","status":"completed"},{"completedAt":"2026-01-08T18:49:04Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-01-08T18:49:00Z","status":"completed"},{"completedAt":"2026-01-08T18:49:04Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-01-08T18:49:04Z","status":"completed"},{"completedAt":"2026-01-08T18:49:04Z","conclusion":"success","name":"Run example","number":7,"startedAt":"2026-01-08T18:49:04Z","status":"completed"},{"completedAt":"2026-01-08T18:49:05Z","conclusion":"success","name":"Post Cache cargo dependencies","number":13,"startedAt":"2026-01-08T18:49:04Z","status":"completed"},{"completedAt":"2026-01-08T18:49:05Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":14,"startedAt":"2026-01-08T18:49:05Z","status":"completed"},{"completedAt":"2026-01-08T18:49:05Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-01-08T18:49:05Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834374349"},{"completedAt":"2026-01-08T18:50:01Z","conclusion":"success","databaseId":59834453397,"name":"Build Package","startedAt":"2026-01-08T18:49:46Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:49:47Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:49:46Z","status":"completed"},{"completedAt":"2026-01-08T18:49:48Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:49:47Z","status":"completed"},{"completedAt":"2026-01-08T18:49:50Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T18:49:48Z","status":"completed"},{"completedAt":"2026-01-08T18:49:50Z","conclusion":"success","name":"Cache cargo dependencies","number":4,"startedAt":"2026-01-08T18:49:50Z","status":"completed"},{"completedAt":"2026-01-08T18:49:58Z","conclusion":"success","name":"Build release","number":5,"startedAt":"2026-01-08T18:49:50Z","status":"completed"},{"completedAt":"2026-01-08T18:49:59Z","conclusion":"success","name":"Package crate","number":6,"startedAt":"2026-01-08T18:49:58Z","status":"completed"},{"completedAt":"2026-01-08T18:50:00Z","conclusion":"success","name":"Post Cache cargo dependencies","number":11,"startedAt":"2026-01-08T18:49:59Z","status":"completed"},{"completedAt":"2026-01-08T18:50:00Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":12,"startedAt":"2026-01-08T18:50:00Z","status":"completed"},{"completedAt":"2026-01-08T18:50:00Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-01-08T18:50:00Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834453397"},{"completedAt":"2026-01-08T18:50:25Z","conclusion":"failure","databaseId":59834479617,"name":"Auto Release","startedAt":"2026-01-08T18:50:03Z","status":"completed","steps":[{"completedAt":"2026-01-08T18:50:05Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-01-08T18:50:04Z","status":"completed"},{"completedAt":"2026-01-08T18:50:06Z","conclusion":"success","name":"Run actions/checkout@v4","number":2,"startedAt":"2026-01-08T18:50:05Z","status":"completed"},{"completedAt":"2026-01-08T18:50:08Z","conclusion":"success","name":"Set up Rust","number":3,"startedAt":"2026-01-08T18:50:06Z","status":"completed"},{"completedAt":"2026-01-08T18:50:11Z","conclusion":"success","name":"Setup Node.js","number":4,"startedAt":"2026-01-08T18:50:08Z","status":"completed"},{"completedAt":"2026-01-08T18:50:11Z","conclusion":"success","name":"Configure git","number":5,"startedAt":"2026-01-08T18:50:11Z","status":"completed"},{"completedAt":"2026-01-08T18:50:11Z","conclusion":"success","name":"Determine bump type from changelog fragments","number":6,"startedAt":"2026-01-08T18:50:11Z","status":"completed"},{"completedAt":"2026-01-08T18:50:11Z","conclusion":"success","name":"Check if version already released","number":7,"startedAt":"2026-01-08T18:50:11Z","status":"completed"},{"completedAt":"2026-01-08T18:50:11Z","conclusion":"skipped","name":"Collect changelog and bump version","number":8,"startedAt":"2026-01-08T18:50:11Z","status":"completed"},{"completedAt":"2026-01-08T18:50:11Z","conclusion":"success","name":"Get current version","number":9,"startedAt":"2026-01-08T18:50:11Z","status":"completed"},{"completedAt":"2026-01-08T18:50:19Z","conclusion":"success","name":"Build release","number":10,"startedAt":"2026-01-08T18:50:11Z","status":"completed"},{"completedAt":"2026-01-08T18:50:19Z","conclusion":"success","name":"Publish to crates.io","number":11,"startedAt":"2026-01-08T18:50:19Z","status":"completed"},{"completedAt":"2026-01-08T18:50:24Z","conclusion":"failure","name":"Create GitHub Release","number":12,"startedAt":"2026-01-08T18:50:19Z","status":"completed"},{"completedAt":"2026-01-08T18:50:24Z","conclusion":"skipped","name":"Post Setup Node.js","number":23,"startedAt":"2026-01-08T18:50:24Z","status":"completed"},{"completedAt":"2026-01-08T18:50:24Z","conclusion":"success","name":"Post Run actions/checkout@v4","number":24,"startedAt":"2026-01-08T18:50:24Z","status":"completed"},{"completedAt":"2026-01-08T18:50:24Z","conclusion":"success","name":"Complete job","number":25,"startedAt":"2026-01-08T18:50:24Z","status":"completed"}],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834479617"},{"completedAt":"2026-01-08T18:50:01Z","conclusion":"skipped","databaseId":59834479814,"name":"Instant Release","startedAt":"2026-01-08T18:50:01Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834479814"}],"name":"Rust CI/CD","status":"completed","workflowName":"Rust CI/CD"} From 1decd204c550527fbfc5fa5d9d4177258a874ac2 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:24:34 +0000 Subject: [PATCH 3/7] docs(readme): add registry-version badges across all READMEs Adds shields.io live registry badges so a reader can immediately see whether a given package is published and at what version. The root README also documents that a "not found" badge means the publish never happened. --- README.md | 11 +++++++++++ csharp/README.md | 5 +++++ js/README.md | 5 +++++ python/README.md | 4 +++- rust/README.md | 5 +++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3938aab..d9383eb 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,19 @@ [![Python CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/python.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/python.yml) [![Rust CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/rust.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/rust.yml) [![C# CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/csharp.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/csharp.yml) + +### Package versions + +[![npm](https://img.shields.io/npm/v/lino-objects-codec?label=npm&logo=npm)](https://www.npmjs.com/package/lino-objects-codec) +[![PyPI](https://img.shields.io/pypi/v/lino-objects-codec?label=PyPI&logo=pypi&logoColor=white)](https://pypi.org/project/lino-objects-codec/) +[![crates.io](https://img.shields.io/crates/v/lino-objects-codec?label=crates.io&logo=rust)](https://crates.io/crates/lino-objects-codec) +[![NuGet](https://img.shields.io/nuget/v/Lino.Objects.Codec?label=NuGet&logo=nuget)](https://www.nuget.org/packages/Lino.Objects.Codec) [![Python Version](https://img.shields.io/pypi/pyversions/lino-objects-codec.svg)](https://pypi.org/project/lino-objects-codec/) +> A "no badge / not found" state on any of the registry badges above means the corresponding +> package has not been published yet for that version. The badges read live from the package +> registries, so they always reflect the latest published state. + Universal serialization library to encode/decode objects to/from Links Notation format. Available in **Python**, **JavaScript**, **Rust**, and **C#** with identical functionality and API design. ## 🌍 Multi-Language Support diff --git a/csharp/README.md b/csharp/README.md index 5e424a8..41297c7 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -1,5 +1,10 @@ # lino-objects-codec (C#) +[![C# CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/csharp.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/csharp.yml) +[![NuGet](https://img.shields.io/nuget/v/Lino.Objects.Codec?label=NuGet&logo=nuget)](https://www.nuget.org/packages/Lino.Objects.Codec) +[![NuGet downloads](https://img.shields.io/nuget/dt/Lino.Objects.Codec?label=downloads)](https://www.nuget.org/packages/Lino.Objects.Codec) +[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](https://unlicense.org/) + A C# library for working with Links Notation format. This library provides universal serialization/deserialization for C# objects with circular reference support. ## Features diff --git a/js/README.md b/js/README.md index 428c9e0..3282d63 100644 --- a/js/README.md +++ b/js/README.md @@ -1,5 +1,10 @@ # lino-objects-codec (JavaScript) +[![JS CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/js.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/js.yml) +[![npm](https://img.shields.io/npm/v/lino-objects-codec?label=npm&logo=npm)](https://www.npmjs.com/package/lino-objects-codec) +[![npm downloads](https://img.shields.io/npm/dm/lino-objects-codec.svg)](https://www.npmjs.com/package/lino-objects-codec) +[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](https://unlicense.org/) + A JavaScript library for working with Links Notation format. This library provides: - Universal serialization/deserialization for JavaScript objects with circular reference support diff --git a/python/README.md b/python/README.md index ad5caf3..5340fb7 100644 --- a/python/README.md +++ b/python/README.md @@ -1,7 +1,9 @@ # lino-objects-codec (Python) -[![Tests](https://github.com/link-foundation/lino-objects-codec/actions/workflows/test.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/test.yml) +[![Python CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/python.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/python.yml) +[![PyPI](https://img.shields.io/pypi/v/lino-objects-codec?label=PyPI&logo=pypi&logoColor=white)](https://pypi.org/project/lino-objects-codec/) [![Python Version](https://img.shields.io/pypi/pyversions/lino-objects-codec.svg)](https://pypi.org/project/lino-objects-codec/) +[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](https://unlicense.org/) A Python library to encode/decode objects to/from Links Notation format. This library provides universal serialization and deserialization for Python objects, with built-in support for circular references and complex object graphs. diff --git a/rust/README.md b/rust/README.md index 0964f6a..251b405 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,5 +1,10 @@ # lino-objects-codec (Rust) +[![Rust CI](https://github.com/link-foundation/lino-objects-codec/actions/workflows/rust.yml/badge.svg)](https://github.com/link-foundation/lino-objects-codec/actions/workflows/rust.yml) +[![crates.io](https://img.shields.io/crates/v/lino-objects-codec?label=crates.io&logo=rust)](https://crates.io/crates/lino-objects-codec) +[![docs.rs](https://img.shields.io/docsrs/lino-objects-codec?label=docs.rs)](https://docs.rs/lino-objects-codec) +[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](https://unlicense.org/) + Rust implementation of the Links Notation Objects Codec - a universal serialization library to encode/decode objects to/from Links Notation format. ## Installation From 6451e42ffde5dad414e958a09cfdc72189b32665 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:26:25 +0000 Subject: [PATCH 4/7] fix(ci): probe registry instead of git tag, fail loud on missing token Auto-release jobs for Python, Rust and C# previously checked the local Git tag to decide whether to publish. Once the workflow itself created that tag the gate flipped to "already released" forever, even though the package never actually reached PyPI / crates.io / NuGet (silent skip when the publish secret was missing). * Replace the tag check with an HTTP probe of the registry (200 = already published; otherwise publish). The pipeline is now self-healing. * Promote missing CARGO_REGISTRY_TOKEN / NUGET_API_KEY from a "::warning:: exit 0" to a "::error:: exit 1" so secrets-not-configured is impossible to mistake for success. * On retry, treat crates.io's "already uploaded/exists" as a non-fatal outcome to keep the pipeline idempotent. * For PyPI publish, enable verbose + skip-existing so first-publish diagnostics surface and re-runs are idempotent. See docs/case-studies/issue-25/README.md for the failure timeline and root-cause analysis. --- .github/workflows/csharp.yml | 44 +++++++++++++++++++------------ .github/workflows/python.yml | 24 +++++++++++++---- .github/workflows/rust.yml | 50 ++++++++++++++++++++++++++++-------- 3 files changed, 85 insertions(+), 33 deletions(-) diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 13418ad..f3d2570 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -207,14 +207,22 @@ jobs: run: | # Get current version from csproj CURRENT_VERSION=$(grep -Po '(?<=)[^<]*' src/Lino.Objects.Codec/Lino.Objects.Codec.csproj) + PACKAGE_ID=$(grep -Po '(?<=)[^<]*' src/Lino.Objects.Codec/Lino.Objects.Codec.csproj) + PACKAGE_ID_LC=$(echo "$PACKAGE_ID" | tr '[:upper:]' '[:lower:]') echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - - # Check if tag exists - if git rev-parse "csharp-v$CURRENT_VERSION" >/dev/null 2>&1; then - echo "Tag csharp-v$CURRENT_VERSION already exists, skipping release" + echo "package_id=$PACKAGE_ID" >> $GITHUB_OUTPUT + + # Decide based on the registry, not on the local git tag. NuGet's flat-container index + # returns 200 for an existing package id and 404 otherwise; the version-specific .nuspec + # endpoint pinpoints whether a particular version is published. See + # docs/case-studies/issue-25/README.md for why we no longer trust the tag. + STATUS=$(curl -sS -o /dev/null -w '%{http_code}' "https://api.nuget.org/v3-flatcontainer/${PACKAGE_ID_LC}/${CURRENT_VERSION}/${PACKAGE_ID_LC}.nuspec") + echo "NuGet HTTP status for ${PACKAGE_ID}@${CURRENT_VERSION}: ${STATUS}" + if [ "$STATUS" = "200" ]; then + echo "Version ${PACKAGE_ID}@${CURRENT_VERSION} already on NuGet, skipping publish" echo "should_release=false" >> $GITHUB_OUTPUT else - echo "New version detected: $CURRENT_VERSION" + echo "Version ${PACKAGE_ID}@${CURRENT_VERSION} is NOT on NuGet, will publish" echo "should_release=true" >> $GITHUB_OUTPUT fi @@ -232,13 +240,14 @@ jobs: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} run: | if [ -z "$NUGET_API_KEY" ]; then - echo "::warning::NUGET_API_KEY not set, skipping publish to NuGet" - else - dotnet nuget push artifacts/*.nupkg \ - --api-key "$NUGET_API_KEY" \ - --source https://api.nuget.org/v3/index.json \ - --skip-duplicate + echo "::error::NUGET_API_KEY is not set; cannot publish to NuGet." + echo "Configure NUGET_API_KEY as a repository secret. See docs/case-studies/issue-25/README.md." + exit 1 fi + dotnet nuget push artifacts/*.nupkg \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate - name: Create GitHub Release if: steps.version_check.outputs.should_release == 'true' @@ -304,13 +313,14 @@ jobs: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} run: | if [ -z "$NUGET_API_KEY" ]; then - echo "::warning::NUGET_API_KEY not set, skipping publish to NuGet" - else - dotnet nuget push artifacts/*.nupkg \ - --api-key "$NUGET_API_KEY" \ - --source https://api.nuget.org/v3/index.json \ - --skip-duplicate + echo "::error::NUGET_API_KEY is not set; cannot publish to NuGet." + echo "Configure NUGET_API_KEY as a repository secret. See docs/case-studies/issue-25/README.md." + exit 1 fi + dotnet nuget push artifacts/*.nupkg \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate - name: Create GitHub Release if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 9556ce1..f6ab6e8 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -208,14 +208,20 @@ jobs: run: | # Get current version from pyproject.toml CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' pyproject.toml) + PACKAGE_NAME=$(grep -Po '(?<=^name = ")[^"]*' pyproject.toml | head -1) echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - - # Check if tag exists - if git rev-parse "python-v$CURRENT_VERSION" >/dev/null 2>&1; then - echo "Tag python-v$CURRENT_VERSION already exists, skipping release" + echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT + + # Decide based on the registry, not on the local git tag. PyPI returns 200 for an + # existing version's JSON metadata and 404 otherwise. See + # docs/case-studies/issue-25/README.md for why the tag is not a reliable proxy. + STATUS=$(curl -sS -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/${PACKAGE_NAME}/${CURRENT_VERSION}/json") + echo "PyPI HTTP status for ${PACKAGE_NAME}@${CURRENT_VERSION}: ${STATUS}" + if [ "$STATUS" = "200" ]; then + echo "Version ${PACKAGE_NAME}@${CURRENT_VERSION} already on PyPI, skipping publish" echo "should_release=false" >> $GITHUB_OUTPUT else - echo "New version detected: $CURRENT_VERSION" + echo "Version ${PACKAGE_NAME}@${CURRENT_VERSION} is NOT on PyPI, will publish" echo "should_release=true" >> $GITHUB_OUTPUT fi @@ -228,9 +234,14 @@ jobs: - name: Publish to PyPI if: steps.version_check.outputs.should_release == 'true' + # Uses PyPI's OIDC Trusted Publisher flow (id-token: write below). If you see "Trusted + # Publisher … not configured" in the logs, configure it for this repo + workflow on PyPI. + # See docs/case-studies/issue-25/README.md for the broader context. uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: python/dist/ + verbose: true + skip-existing: true - name: Create GitHub Release if: steps.version_check.outputs.should_release == 'true' @@ -306,9 +317,12 @@ jobs: - name: Publish to PyPI if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + # See note above: relies on PyPI Trusted Publisher; see docs/case-studies/issue-25/README.md. uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: python/dist/ + verbose: true + skip-existing: true - name: Create GitHub Release if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5aa13ee..0804f6c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -290,14 +290,21 @@ jobs: run: | # Get current version from Cargo.toml CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml) + PACKAGE_NAME=$(grep -Po '(?<=^name = ")[^"]*' Cargo.toml | head -1) echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - - # Check if tag exists - if git rev-parse "rust-v$CURRENT_VERSION" >/dev/null 2>&1; then - echo "Tag rust-v$CURRENT_VERSION already exists" + echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT + + # Decide based on the registry, not on the local git tag. + # A 200 from crates.io means the package@version is already published; otherwise we should + # publish. This makes the pipeline self-healing if a previous publish was silently skipped + # (see docs/case-studies/issue-25/README.md). + STATUS=$(curl -sS -o /dev/null -w '%{http_code}' "https://crates.io/api/v1/crates/${PACKAGE_NAME}/${CURRENT_VERSION}") + echo "crates.io HTTP status for ${PACKAGE_NAME}@${CURRENT_VERSION}: ${STATUS}" + if [ "$STATUS" = "200" ]; then + echo "Version ${PACKAGE_NAME}@${CURRENT_VERSION} already on crates.io, skipping publish" echo "should_release=false" >> $GITHUB_OUTPUT else - echo "New version detected: $CURRENT_VERSION" + echo "Version ${PACKAGE_NAME}@${CURRENT_VERSION} is NOT on crates.io, will publish" echo "should_release=true" >> $GITHUB_OUTPUT fi @@ -329,10 +336,22 @@ jobs: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} run: | if [ -z "$CARGO_REGISTRY_TOKEN" ]; then - echo "::warning::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set, skipping publish to crates.io" - else - cargo publish + echo "::error::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set; cannot publish to crates.io." + echo "Configure one of these as a repository secret. See docs/case-studies/issue-25/README.md." + exit 1 + fi + # `cargo publish` exits non-zero on retry if the version already exists; we tolerate that + # because the registry probe in version_check already proved the version is missing, so a + # late "already exists" error means a parallel run won the race -- treat as success. + if ! OUT=$(cargo publish 2>&1); then + echo "$OUT" + if echo "$OUT" | grep -qE 'already (uploaded|exists)'; then + echo "::warning::Version was published by another run between probe and publish; treating as success." + exit 0 + fi + exit 1 fi + echo "$OUT" - name: Create GitHub Release if: steps.version_check.outputs.should_release == 'true' @@ -400,10 +419,19 @@ jobs: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} run: | if [ -z "$CARGO_REGISTRY_TOKEN" ]; then - echo "::warning::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set, skipping publish to crates.io" - else - cargo publish + echo "::error::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set; cannot publish to crates.io." + echo "Configure one of these as a repository secret. See docs/case-studies/issue-25/README.md." + exit 1 + fi + if ! OUT=$(cargo publish 2>&1); then + echo "$OUT" + if echo "$OUT" | grep -qE 'already (uploaded|exists)'; then + echo "::warning::Version is already on crates.io; treating manual re-run as success." + exit 0 + fi + exit 1 fi + echo "$OUT" - name: Create GitHub Release if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' From cf7453da754e01e9b909e6cb888da90261752081 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:28:51 +0000 Subject: [PATCH 5/7] fix(python): support --tag-prefix in create_github_release.py The python.yml workflow passes --tag-prefix python-v to this script in both auto-release (line 255) and manual-release (line 336) jobs, but the script's argparse did not declare the option, so any release attempt would fail with "unrecognized arguments". This adds the flag with default "v" (preserving prior behavior for direct callers) and propagates it through create_release() to build the tag. Mirrors the rust/scripts/create-github-release.mjs and csharp/scripts/create-github-release.mjs interface, which both expose --tag-prefix with language-specific defaults. Refs: docs/case-studies/issue-25/README.md --- python/scripts/create_github_release.py | 28 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/python/scripts/create_github_release.py b/python/scripts/create_github_release.py index 6d62d3a..8a3a97f 100755 --- a/python/scripts/create_github_release.py +++ b/python/scripts/create_github_release.py @@ -3,10 +3,10 @@ Create a GitHub release from CHANGELOG.md content. Usage: - python scripts/create_github_release.py --version VERSION --repository REPO + python scripts/create_github_release.py --version VERSION --repository REPO [--tag-prefix PREFIX] Example: - python scripts/create_github_release.py --version 1.2.3 --repository owner/repo + python scripts/create_github_release.py --version 1.2.3 --repository owner/repo --tag-prefix python-v Environment variables: GH_TOKEN or GITHUB_TOKEN: GitHub token for authentication @@ -72,10 +72,15 @@ def extract_changelog_entry(changelog_path: Path, version: str) -> str: def create_release( - version: str, repository: str, release_notes: str, prerelease: bool = False + version: str, + repository: str, + release_notes: str, + prerelease: bool = False, + tag_prefix: str = "v", ) -> None: """Create a GitHub release using gh CLI.""" - tag = f"v{version}" + tag = f"{tag_prefix}{version}" + title = f"Python {version}" if tag_prefix == "python-v" else tag print(f"\nCreating GitHub release for {tag}...") print(f"Repository: {repository}") @@ -90,7 +95,7 @@ def create_release( "--repo", repository, "--title", - tag, + title, "--notes", release_notes, ] @@ -124,6 +129,11 @@ def main() -> int: action="store_true", help="Mark as prerelease", ) + parser.add_argument( + "--tag-prefix", + default="v", + help='Tag prefix for the release (e.g. "python-v"); default "v"', + ) args = parser.parse_args() @@ -154,7 +164,13 @@ def main() -> int: release_notes = extract_changelog_entry(changelog_path, args.version) # Create release - create_release(args.version, args.repository, release_notes, args.prerelease) + create_release( + args.version, + args.repository, + release_notes, + args.prerelease, + args.tag_prefix, + ) return 0 From d75c8e9765f0a46a76e82f9f04cc0e5cdb7c535b Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:29:21 +0000 Subject: [PATCH 6/7] chore: drop placeholder .gitkeep created at PR-init time --- .gitkeep | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index d78678e..0000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-05-03T11:13:09.664Z for PR creation at branch issue-25-76b0e5ea19d6 for issue https://github.com/link-foundation/lino-objects-codec/issues/25 \ No newline at end of file From 240826f47ef56d52ff081e6a53c7e6bef4941bc6 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 3 May 2026 11:31:43 +0000 Subject: [PATCH 7/7] chore(js): add changeset for README badge update The JS workflow's any-code-changed detector flags non-JS workflow file edits as code changes, which forces a changeset on this PR even though no JS source moves. Marking as patch because js/README.md was touched (badges) and that does ship to npm via the README field. --- js/.changeset/issue-25-readme-badges.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 js/.changeset/issue-25-readme-badges.md diff --git a/js/.changeset/issue-25-readme-badges.md b/js/.changeset/issue-25-readme-badges.md new file mode 100644 index 0000000..f42fc1c --- /dev/null +++ b/js/.changeset/issue-25-readme-badges.md @@ -0,0 +1,6 @@ +--- +'lino-objects-codec': patch +--- + +Add registry-version, CI, and license badges to the JavaScript README so the +package's published state on npm is visible at a glance. No code changes.