Metadata-Version: 2.5
Name: bompage
Version: 1.0.2
Summary: A generic, server-less system to track SBOM history of N components and publish a static dashboard
Project-URL: Homepage, https://gitlab.com/op_so/pages/bompage
Project-URL: Documentation, https://op_so.gitlab.io/pages/bompage/
Author-email: FX Soubirou <soubirou@yahoo.fr>
License: MIT
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.14
Requires-Dist: pydantic>=2.13.5
Requires-Dist: typer>=0.27.2
Description-Content-Type: text/markdown

# `BOMpage`

![BOMpage](https://gitlab.com/op_so/pages/bompage/-/raw/main/web-src/bompage.png)

[![Software License](https://img.shields.io/badge/license-MIT-informational.svg?style=for-the-badge)](LICENSE)
[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release&style=for-the-badge)](https://github.com/semantic-release/semantic-release)
[![Pipeline Status](https://img.shields.io/gitlab/pipeline-status/op_so%2Fpages%2Fbompage?style=for-the-badge)](https://gitlab.com/op_so/pages/bompage/pipelines)

[![Built with Material for MkDocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?style=for-the-badge&logo=MaterialForMkDocs&logoColor=white)](https://op_so.gitlab.io/pages/bompage/) Source code documentation

A generic, server-less system to centralise the SBOM history of **N components**
in a dedicated Git repository and publish a static dashboard (GitLab / GitHub
Pages) plus an optional static REST API.

<!-- markdownlint-disable MD033 -->
> **Status:** SPDX and CycloneDX. Covered: last known state (&sect;7.1),
> per-package timeline &amp; recent changes (&sect;7.2), data freshness (&sect;7.5),
> transverse search (&sect;7.3) and cross-component version drift (&sect;7.4),
> snapshot retention (&sect;5.4), plus the reusable CI push component (&sect;9).
> Not yet: the arbitrary two-date diff (&sect;7.6) and the versioned `api/v1/`
> surface (&sect;8).
<!-- markdownlint-enable MD033 -->

## What it does

From a central `bompage` repository laid out as `reports/<component>/sbom-YYYYMMDD[-version].json`,
the `bompage` CLI:

- discovers components dynamically (one directory = one component);
- parses **every dated snapshot** of each component (SPDX and CycloneDX,
  including CycloneDX nested sub-components) into a common inventory model;
- derives, per component: current inventory (name, version, ecosystem from the
  `purl`, licence, strong-copyleft flag), the per-package version timeline, the
  added/removed deltas, a recent-changes feed and a freshness verdict;
- builds a cross-component inverted index (package &rarr; components) and the
  list of active version drifts;
- writes a JSON data tree under `public/data/` (`index.json`, `sbom-data.json`,
  `components/<name>.json`, `packages-index.json`, `drifts.json`) plus a copy of
  each raw manifest;
- ships a **constant** dashboard page (no project data baked in) that fetches
  that tree at runtime: per-component tabs with a stale badge, ecosystem filter,
  client-side search, a recent-changes panel, a click-through version timeline,
  a global transverse search and an "active drifts" view.

Ingestion is the [`push-report`](templates/push-report.yml) reusable GitLab
CI/CD component (spec &sect;9): it `pip install`s a pinned `bompage` release and
runs `bompage push`, which clones the central repo with a scoped token, drops
the SBOM under `reports/<component>/` and commits **only if the content changed**
(anti-noise filter, &sect;10). The logic lives in
[`src/bompage/push.py`](src/bompage/push.py) (it shells out to `git`; no VCS
library).

Retention is `bompage prune` (spec &sect;5.4,
[`src/bompage/prune.py`](src/bompage/prune.py)): it removes snapshots older than
`--keep-days` (default 90) from `reports/` in one ordinary commit &mdash; no
history rewrite &mdash; always keeping each component's most recent snapshot, and
runs as a scheduled job in the central repo's own pipeline. Shared git plumbing
sits in [`src/bompage/_git.py`](src/bompage/_git.py).

The page is built with [Material Web](https://github.com/material-components/material-web)
(Material Design 3) + [lit](https://lit.dev/), bundled by Vite, with Roboto and
a subset of Material Symbols self-hosted (no CDN, no runtime network call).

## Usage

```text
Usage: bompage [OPTIONS] COMMAND [ARGS]...

  Track SBOM history of N components and build a static dashboard.

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  build  Build the static dashboard and its data/ tree from a reports/ tree.
  push   Publish one SBOM into the central repository (clone, commit iff changed, push).
  prune  Remove SBOM snapshots older than --keep-days from the central repository.
```

Every command also accepts `--help`. Full generated reference:
[CLI documentation](https://op_so.gitlab.io/pages/bompage/cli/).

### `bompage build`

Build the static dashboard and its `data/` tree from a `reports/` tree. Runs
locally or in the central repo's Pages job; needs no network access.

| Option | Default | Description |
| --- | --- | --- |
| `--reports PATH` | `reports` | Directory holding one sub-directory per component (must exist). |
| `--output PATH` | `public` | Directory the static site is written to. |
| `--web-dir PATH` | *(packaged assets)* | Pre-built dashboard (`index.html` + `assets/`) to use instead of the one bundled in the wheel. |
| `--stale-after N` | `2` | A component with no new report for more than `N` days is flagged stale (min `0`). |
| `--recent-days N` | `30` | Sliding window, in days, for the dashboard's recent-changes feed (min `1`). |
| `--metadata "key=value"` | — | Global metadata row shown in the dashboard header. Repeatable. |
| `--metadata-component "component:key=value"` | — | Per-component metadata row. Repeatable. |

```bash
bompage build --reports reports --output public \
  --stale-after 2 --recent-days 30 \
  --metadata "team=platform" --metadata-component "app-api:owner=backend"
```

### `bompage push`

Publish one SBOM into the central repository: clone with a scoped token, drop the
file under `reports/<component>/`, and commit **only if the content changed**
(anti-noise filter, spec §10), then push. Requires the `git` binary on `PATH`.

| Option | Default | Description |
| --- | --- | --- |
| `--component TEXT` | *required* | Logical component name (one directory under `reports/`). |
| `--sbom PATH` | *required* | Path to the SBOM file produced by the calling job (must exist). |
| `--repo TEXT` | *required* | HTTPS clone URL of the central `bompage` repository. |
| `--token TEXT` | *required* — env `BOMPAGE_TOKEN` | Scoped write token for the central repository. |
| `--branch TEXT` | `main` | Target branch in the central repository. |
| `--sbom-version TEXT` | — | Version suffix appended to the SBOM file name. |
| `--sbom-format TEXT` | — | Informative (`spdx` \| `cyclonedx`); recorded in the commit message. |
| `--git-user TEXT` | `bompage-ci` | Commit author name. |
| `--git-email TEXT` | `bompage-ci@localhost` | Commit author email. |

```bash
export BOMPAGE_TOKEN=…            # scoped write token
bompage push \
  --component app-api \
  --sbom build/sbom.cdx.json \
  --sbom-format cyclonedx \
  --repo https://gitlab.com/acme/bompage.git
```

### `bompage prune`

Remove SBOM snapshots older than `--keep-days` from `reports/` in a single
ordinary commit — **no history rewrite** — always keeping each component's most
recent snapshot (spec §5.4). Same `git`-on-`PATH` and token requirements as
`push`; runs as a scheduled job in the central repo's own pipeline.

| Option | Default | Description |
| --- | --- | --- |
| `--repo TEXT` | *required* | HTTPS clone URL of the central `bompage` repository. |
| `--token TEXT` | *required* — env `BOMPAGE_TOKEN` | Scoped write token for the central repository. |
| `--branch TEXT` | `main` | Target branch in the central repository. |
| `--keep-days N` | `90` | Snapshots older than `N` days are removed; the most recent snapshot of each component is always kept (min `1`). |
| `--dry-run` | *(off)* | List what would be removed without committing or pushing. |
| `--git-user TEXT` | `bompage-ci` | Commit author name. |
| `--git-email TEXT` | `bompage-ci@localhost` | Commit author email. |

```bash
export BOMPAGE_TOKEN=…
bompage prune --repo https://gitlab.com/acme/bompage.git --keep-days 90 --dry-run
```

### Preview locally

Preview with any static file server (the page uses `fetch`, so it will not run
from `file://`):

```bash
bompage build --reports reports --output public
python3 -m http.server -d public
```

## Installation

### With `Python` environment

- Minimal Python version: 3.14

```bash
python3 -m pip install bompage
bompage --help
```

The published wheel ships the pre-built dashboard, so no JS toolchain is needed
to *use* `bompage`.

### From source

The dashboard assets (`src/bompage/web/`) are a Vite build output and are **not**
committed. JS dependencies are managed with [pnpm](https://pnpm.io). Build them
once before running the tests or `bompage build`:

```bash
brew install pnpm  # or: corepack enable && corepack prepare pnpm@11 --activate
fnm use            # Node 24, per .node-version
pnpm install --frozen-lockfile
pnpm run build:web # -> src/bompage/web/  (or: task 00:010-build-web)
```

The dashboard sources are checked with Biome + `tsc` (`pnpm run lint:web`,
`pnpm run typecheck:web`, `pnpm run format:web`; `task 00:015-check-web`).
`task 00:190-check` runs the whole static battery, `task 00:290-check-all` adds
the tests. Tests that need the built page skip cleanly until it is built; the
browser smoke test is `uv run pytest -m e2e` (needs `uv run playwright install
chromium`).

### Tests

```bash
task 00:010-build-web
uv run python scripts/gen-demo-data.py
uv run python -m http.server -d public 8000
```

## Deployment

The `deploy/` directory is a scaffold for bootstrapping a central `bompage`
repository &mdash; the pages job plus a scheduled `bompage prune` job (see
[`deploy/README.md`](deploy/README.md)).

## Authors

<!-- vale off -->
- **FX Soubirou** - *Initial work* - [GitLab repositories](https://gitlab.com/op_so)
<!-- vale on -->

## License

<!-- vale off -->
This program is free software: you can redistribute it and/or modify it under the terms of the MIT License (MIT).
See the [LICENSE](https://opensource.org/licenses/MIT) for details.
<!-- vale on -->
