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
38 changes: 37 additions & 1 deletion site/src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Starlight's footer (edit link, previous and next) plus the project's own line: the
// license, the trademark note and the privacy promise this site keeps.
import Default from "@astrojs/starlight/components/Footer.astro";
import { site } from "../config";
import { openssf, site } from "../config";
---

<Default />
Expand All @@ -18,6 +18,11 @@ import { site } from "../config";
This site sets no cookies, has no analytics and loads nothing from other sites. It is hosted on GitHub Pages
behind Cloudflare, which see visitors' IP addresses, as any web host does.
</p>
<p>
<a class="kotiko-badge" href={openssf.url} aria-label={`OpenSSF Best Practices: ${openssf.level}`}>
<span>OpenSSF Best Practices</span> <span class={`level level-${openssf.level}`}>{openssf.level}</span>
</a>
</p>
<p>
<a href="/privacy/">Privacy policy</a> 路 <a href={site.repo}>Source code</a> 路 <a href={site.issues}>Report a problem</a>
</p>
Expand All @@ -34,4 +39,35 @@ import { site } from "../config";
.kotiko-footer p + p {
margin-top: 0.5rem;
}
/* The badge, drawn here rather than loaded from bestpractices.dev (see config.ts). */
.kotiko-badge {
display: inline-flex;
border-radius: 4px;
overflow: hidden;
font-size: var(--sl-text-xs);
line-height: 1.8;
text-decoration: none;
border: 1px solid var(--sl-color-hairline-light);
}
.kotiko-badge span {
padding: 0 0.5rem;
}
.kotiko-badge span:first-child {
background: var(--sl-color-gray-6);
color: var(--sl-color-white);
}
.kotiko-badge .level {
text-transform: capitalize;
font-weight: 600;
color: #1b1b1b;
}
.kotiko-badge .level-passing {
background: #c9a227;
}
.kotiko-badge .level-silver {
background: #c0c6cc;
}
.kotiko-badge .level-gold {
background: #e2b437;
}
</style>
8 changes: 8 additions & 0 deletions site/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export const site = {
releases: "https://github.com/ScriptKittyOS/kotiko/releases",
issues: "https://github.com/ScriptKittyOS/kotiko/issues",
};

// The OpenSSF Best Practices badge (https://www.bestpractices.dev/projects/15259). The site
// loads nothing from other hosts, so it shows the level as text instead of the live image
// the README uses: when the entry reaches a new level, change `level` here.
export const openssf = {
level: "silver" as "passing" | "silver" | "gold",
url: "https://www.bestpractices.dev/projects/15259",
};
29 changes: 29 additions & 0 deletions site/tests/badge.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2026 ScriptKittyOS and the Kotiko contributors
// SPDX-License-Identifier: Apache-2.0

// The OpenSSF Best Practices badge in the footer: every page links to the entry and shows
// the level set in src/config.ts, drawn in CSS rather than loaded from another host.
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { ensureBuilt, read, text, SITE } from "./helpers.mjs";

const level = fs.readFileSync(path.join(SITE, "src/config.ts"), "utf8").match(/level:\s*"(passing|silver|gold)"/)?.[1];

test("config.ts names a badge level", () => {
assert.ok(level, "src/config.ts sets openssf.level");
});

for (const page of ["index.html", "install/index.html", "privacy/index.html"]) {
test(`${page} shows the ${level} badge linked to the entry`, () => {
ensureBuilt();
const html = read(page);
const badge = html.match(/<a class="kotiko-badge[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/);
assert.ok(badge, "the footer has the badge");
assert.equal(badge[1], "https://www.bestpractices.dev/projects/15259");
assert.equal(text(badge[2]), `OpenSSF Best Practices ${level}`);
assert.match(badge[0], new RegExp(`aria-label="OpenSSF Best Practices: ${level}"`));
assert.doesNotMatch(html, /bestpractices\.dev\/projects\/15259\/badge/, "no image loaded from bestpractices.dev");
});
}