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' 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/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"} 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. 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/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 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