Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ jobs:
- run: pnpm run lint
- run: pnpm run test
- run: pnpm run build

# Generate both SBOMs and verify them against package.json, so a dependency
# change that breaks the SBOM turns the pull request red before it reaches a release.
- name: Generate and verify SBOM
run: |
pnpm run sbom
pnpm run sbom:verify
21 changes: 18 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ jobs:
- run: pnpm run test
- run: pnpm run build

# Generate and verify the SBOMs BEFORE anything is published. A failure here aborts
# the job while the tag can still be re-cut: no release ever ships without a valid SBOM,
# and no version reaches the npm staging queue without one.
- name: Generate and verify SBOM
run: |
pnpm run sbom
pnpm run sbom:verify

# Uploads the tarball to the npm staging queue via OIDC — no NPM_TOKEN needed
# (pnpm auto-detects the GitHub Actions OIDC token via id-token: write).
# Provenance is generated automatically for public repos. The version does NOT
Expand All @@ -62,8 +70,15 @@ jobs:
run: pnpm stage publish --access public --no-git-checks

# Created as a draft so the GitHub release is published by hand together with
# the npm approval — keeping both behind the same human 2FA gate.
- name: Create draft GitHub release
run: gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag --draft
# the npm approval — keeping both behind the same human 2FA gate. Both SBOMs are
# attached as assets; the --notes text names sbom.cdx.json as the authoritative
# document so an auditor answering a supplier assessment picks the right one.
- name: Create draft GitHub release with SBOM assets
run: |
gh release create "${GITHUB_REF_NAME}" \
--generate-notes --verify-tag --draft \
--notes "**SBOM:** \`sbom.cdx.json\` is the authoritative CycloneDX SBOM for this release (runtime dependencies, including peer dependencies). \`sbom-dev.cdx.json\` covers the build toolchain only and is not authoritative." \
'sbom/sbom.cdx.json#Authoritative runtime SBOM (CycloneDX)' \
'sbom/sbom-dev.cdx.json#Build toolchain SBOM (non-authoritative)'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
dist/
sbom/
*.tsbuildinfo
.env
.claude/settings.local.json
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ Import brand CSS in your app's stylesheet:
import { de, en } from "@open-elements/ui";
```

## Software Bill of Materials (SBOM)

Every release publishes two [CycloneDX](https://cyclonedx.org/) 1.7 SBOMs as assets on its
[GitHub Release](https://github.com/OpenElementsLabs/open-elements-ui/releases), so a specific
published version can be obtained without an `npm install`:

| Asset | Contents | Authoritative? |
| --- | --- | --- |
| `sbom.cdx.json` | Runtime dependencies (transitive) plus the library's peer dependencies | **Yes** — use this for supplier assessments (Cyber Resilience Act) |
| `sbom-dev.cdx.json` | The build toolchain (`devDependencies`) | No — provided for transparency only |

Both are generated locally with the pinned `pnpm` (`pnpm sbom`) and verified in CI on every pull
request, so a dependency change that breaks the SBOM turns the build red. A release cannot ship without
a valid SBOM.

```bash
pnpm sbom # writes sbom/sbom.cdx.json and sbom/sbom-dev.cdx.json (gitignored)
pnpm sbom:verify # validates both against the CycloneDX 1.7 schema and package.json
```

**Peer dependencies.** `radix-ui`, `@base-ui/react`, `lucide-react`, `react` and `react-dom` are peer
dependencies: the consumer supplies them. `pnpm sbom` alone omits them, so they are added to
`sbom.cdx.json` and marked with a `cdx:npm:peer` property holding the declared range. The **version**
recorded for each peer is the one resolved in _this repository's_ lockfile, not the one a consumer
installs — it changes when we bump our own devDependencies, even though nothing changes for the
consumer. Read a peer's `cdx:npm:peer` range, not its pinned version, as the requirement.

## Releasing a New Version

Every release must be published to npm **and** have a corresponding Git tag and GitHub Release.
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"typecheck": "tsc --noEmit",
"sbom": "node scripts/generate-sbom.mjs",
"sbom:verify": "node scripts/verify-sbom.mjs",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
Expand Down Expand Up @@ -80,6 +82,10 @@
"react-dom": "^19.0.0",
"@base-ui/react": "^1.3.0",
"radix-ui": "^1.4.0",
"lucide-react": "^0.500.0"
"lucide-react": "^0.500.0",
"@cyclonedx/cyclonedx-library": "^10.2.0",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"ajv-formats-draft2019": "^1.6.1"
}
}
Loading
Loading