Hamburger Cross Icon
endoflife.date Collector - Lunar Collector

endoflife.date Collector

Collector Beta Languages

Detects pinned runtime versions (Node.js, Python, Ruby, Go, Java, .NET, PHP) from standard repo files and resolves each against https://endoflife.date. Writes EOL/support data to `.lang.<language>.eol` so policies can flag services running on EOL or out-of-support runtimes.

Add endoflife to your lunar-config.yml:
uses: github://earthly/lunar-lib/collectors/endoflife@v1.0.5

What This Integration Collects

This integration includes 1 collector that gather metadata from your systems.

Collector code

runtime

Walks the cloned repo for runtime version pins across Node.js, Python, Ruby, Go, Java, .NET, and PHP. For each detected runtime, queries https://endoflife.date/api/.json and matches the detected version to a cycle (e.g. Go 1.21.5 → cycle 1.21). Writes the normalized result to .lang.<language>.eol with cycle, detected_version, is_eol, is_supported, eol_date, support_until, lts, latest_in_cycle, and source. Stores the raw matched cycle object under .lang.<language>.native.endoflife.cycle (alongside a .product string sibling). Runs on the code hook so the data refreshes on every push without depending on a separate cron schedule. If no runtime can be resolved for a given language, the language is silently skipped — only languages with a confirmed pin produce output.

endoflife eol runtime lifecycle support node python ruby go java dotnet php
Book a demo

How Collectors Fit into Lunar

Lunar watches your code and CI/CD systems to collect SDLC data from config files, test results, IaC, deployment configurations, security scans, and more.

Collectors are the automatic data-gathering layer. They extract structured metadata from your repositories and pipelines, feeding it into Lunar's centralized database where guardrails evaluate it to enforce your engineering standards.

Learn How Lunar Works
1
Collectors Gather Data This Integration
Triggered by code changes or CI pipelines, collectors extract metadata from config files, tool outputs, test results, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
Guardrails Enforce Standards
Real-time feedback in PRs and AI workflows

Example Collected Data

This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:

{ } component.json Component JSON
{
  "lang": {
    "go": {
      "version": "1.21",
      "eol": {
        "source": {
          "tool": "endoflife.date",
          "integration": "api",
          "collected_at": "2026-04-27T12:00:00Z"
        },
        "product": "go",
        "cycle": "1.21",
        "detected_version": "1.21",
        "is_eol": true,
        "is_supported": false,
        "eol_date": "2024-08-13",
        "support_until": null,
        "lts": false,
        "latest_in_cycle": "1.21.13"
      },
      "native": {
        "endoflife": {
          "product": "go",
          "cycle": {
            "cycle": "1.21",
            "releaseDate": "2023-08-08",
            "eol": "2024-08-13",
            "latest": "1.21.13",
            "latestReleaseDate": "2024-08-06",
            "lts": false
          }
        }
      }
    },
    "nodejs": {
      "version": "20.11.1",
      "eol": {
        "source": {
          "tool": "endoflife.date",
          "integration": "api",
          "collected_at": "2026-04-27T12:00:00Z"
        },
        "product": "nodejs",
        "cycle": "20",
        "detected_version": "20.11.1",
        "is_eol": false,
        "is_supported": false,
        "eol_date": "2026-04-30",
        "support_until": "2024-10-22",
        "lts": true,
        "latest_in_cycle": "20.19.0"
      },
      "native": {
        "endoflife": {
          "product": "nodejs",
          "cycle": {
            "cycle": "20",
            "releaseDate": "2023-04-18",
            "lts": "2023-10-24",
            "eol": "2026-04-30",
            "latest": "20.19.0",
            "support": "2024-10-22"
          }
        }
      }
    }
  }
}

Configuration

Configure this collector in your lunar-config.yml.

Inputs

Input Required Default Description
endoflife_base_url Optional https://endoflife.date/api endoflife.date API base URL. Override only if you proxy/mirror the API internally.
java_product Optional eclipse-temurin endoflife.date product slug used for Java. There is no generic `java` product — pick the JDK distribution that matches your runtime. Common values: `eclipse-temurin` (default), `amazon-corretto`, `azul-zulu`, `microsoft-build-of-openjdk`, `redhat-build-of-openjdk`, `oracle-jdk`, `sapmachine`, `bellsoft-liberica`, `ibm-semeru-runtime`.
dotnet_product Optional dotnet endoflife.date product slug used for .NET. Use `dotnet` for modern .NET (.NET 5+, .NET Core) or `dotnetfx` for legacy .NET Framework.

Documentation

View on GitHub

endoflife.date Collector

Detect runtime/SDK versions used by a component and check them against the endoflife.date API for end-of-life and active-support status.

Overview

This collector inspects standard pinning files in the cloned repo (.go-version, go.mod, .nvmrc, package.json, .python-version, pyproject.toml, .ruby-version, Gemfile, .java-version, pom.xml, build.gradle, global.json, *.csproj, composer.json) to determine which runtime and version a service runs on. It then queries endoflife.date for the matching product cycle (Go 1.21.5 → cycle 1.21) and writes normalized lifecycle data to .lang.<language>.eol. The endoflife.date API is public — no secrets or accounts are required, only network access to https://endoflife.date. Catches the "neglected legacy service" smell where components are pinned to runtimes past EOL or out of active support.

Collected Data

This collector writes to the following Component JSON paths, where <language> is one of go, nodejs, python, ruby, java, dotnet, php:

Path Type Description
.lang.<language>.eol.source object Tool/integration metadata (tool: "endoflife.date", integration: "api", collected_at)
.lang.<language>.eol.product string endoflife.date product slug (e.g. go, nodejs, eclipse-temurin)
.lang.<language>.eol.cycle string Matched release cycle (e.g. "1.21" for Go, "20" for Node.js)
.lang.<language>.eol.detected_version string The version string as pinned in the repo. May be major.minor only (e.g. "1.21" from a bare go 1.21 directive in go.mod) or a full semver (e.g. "20.11.1" from .nvmrc).
.lang.<language>.eol.is_eol boolean true if eol_date is set and is on or before today
.lang.<language>.eol.is_supported boolean true if the runtime is still in active (non-security-only) support
.lang.<language>.eol.eol_date string | null ISO date when the cycle reaches end-of-life; null if endoflife.date does not declare one
.lang.<language>.eol.support_until string | null ISO date when active support ends (security-only afterward); null for products that don't distinguish support from EOL
.lang.<language>.eol.lts boolean Whether this cycle is an LTS release (also true if endoflife.date returns an LTS-start date)
.lang.<language>.eol.latest_in_cycle string Latest patch in the cycle (e.g. "1.21.13")
.lang.<language>.native.endoflife.product string Same as .lang.<language>.eol.product, kept for native-data parity
.lang.<language>.native.endoflife.cycle object Raw cycle object as returned by https://endoflife.date/api/<product>.json

If a runtime cannot be resolved for a given language (no pin file present, or the pinned version doesn't map to any endoflife.date cycle), nothing is written for that language. Absence of a .lang.<language>.eol key means "we couldn't determine an EOL status" — not "the runtime is supported".

Collectors

Collector Description
runtime Detects pinned runtime versions across Node.js, Python, Ruby, Go, Java, .NET, and PHP, then queries endoflife.date and writes EOL/support data per language. Code hook.

Installation

Add to your lunar-config.yml:

collectors:
  - uses: github://earthly/lunar-lib/collectors/endoflife@v1.0.0
    on: ["domain:your-domain"]
    with:
      # Override only if you mirror the API internally
      # endoflife_base_url: "https://endoflife.date/api"
      # JDK distribution — see "Java distribution" below
      # java_product: "eclipse-temurin"
      # Use "dotnet" for modern .NET, "dotnetfx" for legacy .NET Framework
      # dotnet_product: "dotnet"

Pair it with the endoflife policy to enforce EOL and support guardrails:

policies:
  - uses: github://earthly/lunar-lib/policies/endoflife@v1.0.0
    enforcement: report-pr

No secrets are required.

Version detection

The collector attempts to detect a runtime version per language using the following pin files, in priority order. The first source that yields a parseable version wins.

Language Sources (in order)
Go .go-version, go.mod (go 1.21 or toolchain go1.21.5)
Node.js .nvmrc, .node-version, package.json (engines.node)
Python .python-version, runtime.txt, pyproject.toml (requires-python)
Ruby .ruby-version, Gemfile (ruby '3.2.0')
Java .java-version, pom.xml (<java.version>, <maven.compiler.release>), build.gradle (sourceCompatibility, targetCompatibility)
.NET global.json (sdk.version), first *.csproj/*.fsproj/*.vbproj (<TargetFramework>net8.0</TargetFramework>)
PHP composer.json (require.php / config.platform.php)

If a file declares a range or constraint (>=3.10, ^20.0.0), the collector picks the lowest concrete version that satisfies the constraint, since that's the worst-case the runtime can drop to. If no concrete version can be resolved, the language is skipped.

Cycle matching and support semantics

Each detected version is matched to the most specific endoflife.date cycle (Go 1.21.51.21, Node.js 20.11.120, Python 3.11.73.11, Ruby 3.2.03.2, Java 17.0.917, .NET 8.0.1008, PHP 8.2.108.2). If no matching cycle is found (e.g. a beta/preview version not yet listed), the collector writes nothing for that language and logs a stderr message.

endoflife.date distinguishes two phases for products that have them — active support (the runtime is receiving normal updates including non-security fixes) and security/maintenance (only critical security fixes are backported). The collector exposes both as separate booleans so policies can pick the strictness they want: is_eol is true past eol_date (no support of any kind, including security); is_supported is true only while the runtime is in active support. For products where endoflife.date doesn't expose a separate support field (e.g. Go), support_until is null and is_supported falls back to not is_eol.

Java distribution

There is no generic java product on endoflife.date. The default is eclipse-temurin (the Adoptium successor to AdoptOpenJDK), which is the most common OpenJDK distribution. If your service ships on a different distribution, set the java_product input to one of: amazon-corretto, azul-zulu, bellsoft-liberica, eclipse-temurin (default), ibm-semeru-runtime, microsoft-build-of-openjdk, oracle-jdk, redhat-build-of-openjdk, sapmachine. These mostly track the same upstream OpenJDK release schedule but each has its own EOL/support dates, so picking the right one matters when EOL is days or weeks away.

.NET vs .NET Framework

dotnet_product defaults to dotnet, which covers .NET 5 / 6 / 7 / 8 / 9+ (the cross-platform line). If your component is on legacy .NET Framework 4.x, set dotnet_product: "dotnetfx".

Roadmap: decoupling from per-language detection

Adding endoflife coverage for a new language plugin currently requires a code edit here (a parser for that language's pin file plus an endoflife.date product slug). The intended endpoint is to consume .lang.<language>.version already written by per-language collectors (golang, nodejs, python, etc.) and reduce this collector to a { language → endoflife product slug } map — at which point new language coverage becomes a config-only addition. That migration depends on collector-to-collector ordering / dependency support landing in the lunar platform; until that lands, the duplication of version-file parsing is intentional, since this collector cannot rely on language collectors having run first within the same code-hook cycle. Tracked as a follow-up.

Limitations and behavior notes

  • Runtime pin only — this collector reads pinned runtime versions, not the actual installed version on a deployment target. A go.mod that says go 1.21 doesn't guarantee production runs Go 1.21 — that's a deployment-config concern.
  • Frameworks not covered — endoflife.date covers many frameworks (Spring Boot, Django, Rails, etc.), but this v1 ships runtime-only checks. Framework EOL is on the roadmap.
  • No vendor-specific overrides — Java's distribution choice is a single input applied across all components. Components on different JDKs in the same domain need separate endoflife collector instances.
  • New languages need a code edit — see "Roadmap" above. Adding a new language today means a new detection branch + product slug here; the planned migration to .lang.<language>.version consumption removes that requirement.
  • The collector runs on the code hook, so it fires on each push. The endoflife.date API is small and fast (sub-second responses), so per-push lookups are cheap.
  • Network errors against endoflife.date are non-fatal: the collector logs to stderr and exits 0 without writing partial data. Policies that depend on this collector will skip when no .lang.<language>.eol data is present.
  • Example Component JSON is defined in lunar-collector.yml under example_component_json.

Open Source

This collector is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.

View Repository

Ready to Automate Your Standards?

See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 200+ built-in guardrails.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Automate Now
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo