Files
releases/docs/docusaurus.config.js
T
Charlie Egan d9ba5b4891 website: Import blog from medium (#8898)
This PR imports the blogs from https://medium.com/open-policy-agent into
the docusaurus site.

This is being done as the blog has stopped working due to the custom
domain hosting feature being removed from our tier.

The pages have been imported from manually downloaded webarchive files
from each post to ensure we captured the assets and rendered code
example too.

---------

Signed-off-by: Charlie Egan <charlie_egan@apple.com>
2026-07-16 12:01:24 +01:00

742 lines
24 KiB
JavaScript

const { themes } = require("prism-react-renderer");
const lightCodeTheme = themes.github;
const darkCodeTheme = {
...themes.dracula,
styles: [
...themes.dracula.styles,
{
types: ["atrule"],
style: { color: "rgb(139, 233, 253)" },
},
],
};
const semver = require("semver");
import fs from "fs/promises";
import { loadPages } from "./src/lib/ecosystem/loadPages.js";
import { loadEvents } from "./src/lib/events/loadEvents.js";
import { markdownExportPlugin } from "./src/lib/plugins/markdownExport.js";
import { loadRules } from "./src/lib/projects/regal/loadRules.js";
import { loadSurveyEventData, loadSurveyEventMetadata, loadSurveyQuestions } from "./src/lib/surveys/loadSurveyData.js";
const path = require("path");
const yaml = require("js-yaml");
const baseUrl = "/";
// With JSDoc @type annotations, IDEs can provide config autocompletion
/** @type {import("@docusaurus/types").DocusaurusConfig} */
(
module.exports = {
title: "Open Policy Agent",
tagline: "Policy-based control for cloud native environments",
url: "https://openpolicyagent.org",
baseUrl: baseUrl,
trailingSlash: false,
// when BUILD_VERSION is set (release builds), warn on broken links/anchors so we don't break main
// when not set (PR checks), throw to flag issues for developers
onBrokenLinks: process.env.BUILD_VERSION ? "warn" : "throw",
onBrokenAnchors: process.env.BUILD_VERSION ? "warn" : "throw",
presets: [
[
"@docusaurus/preset-classic",
/** @type {import("@docusaurus/preset-classic").Options} */
{
docs: {
path: "docs",
routeBasePath: "/docs/",
breadcrumbs: false,
sidebarPath: require.resolve("./src/lib/sidebars.js"),
},
blog: {
path: "blog",
routeBasePath: "blog",
blogTitle: "OPA Blog",
blogDescription: "News, releases, and community updates from the Open Policy Agent project.",
postsPerPage: 10,
blogSidebarTitle: "OPA Blog Posts",
blogSidebarCount: "ALL",
showReadingTime: false,
feedOptions: {
type: ["rss", "atom"],
title: "Open Policy Agent Blog",
},
onInlineTags: "ignore",
onUntruncatedBlogPosts: "ignore",
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
},
],
],
customFields: {
buildVersion: process.env.BUILD_VERSION,
},
markdown: {
hooks: { onBrokenMarkdownLinks: "throw" },
mermaid: true,
},
themes: ["@docusaurus/theme-mermaid"],
themeConfig: {
colorMode: {
defaultMode: "light",
disableSwitch: false,
respectPrefersColorScheme: true,
},
metadata: [
{ name: "msapplication-TileColor", content: "#2b5797" },
{ name: "theme-color", content: "#ffffff" },
],
headTags: [
{
tagName: "link",
attributes: {
rel: "icon",
type: "image/png",
href: "/favicon-96x96.png",
sizes: "96x96",
},
},
{
tagName: "link",
attributes: {
rel: "icon",
type: "image/svg+xml",
href: "/favicon.svg",
},
},
{
tagName: "link",
attributes: {
rel: "shortcut icon",
href: "/favicon.ico",
},
},
{
tagName: "link",
attributes: {
rel: "apple-touch-icon",
sizes: "180x180",
href: "/apple-touch-icon.png",
},
},
{
tagName: "meta",
attributes: {
name: "apple-mobile-web-app-title",
content: "OPA",
},
},
{
tagName: "link",
attributes: {
rel: "manifest",
href: "/site.webmanifest",
},
},
],
navbar: {
title: "Open Policy Agent",
logo: {
alt: "OPA Logo",
src: "img/nav/logo.png",
},
items: [
{
// Based on implementation outlined here:
// https://github.com/facebook/docusaurus/issues/7227#issue-1212117180
type: "custom-currentVersionNavbarItem",
position: "left",
},
{ to: "/docs/", label: "Docs", position: "right" },
{
type: "dropdown",
label: "Resources",
position: "right",
items: [
{ to: "/security", label: "Security" },
{ to: "/support", label: "Support" },
{ to: "/community", label: "Community" },
{ to: "/survey", label: "Survey" },
{ to: "/blog", label: "Blog" },
],
},
{
type: "dropdown",
label: "Projects",
position: "right",
items: [
{ to: "/docs", label: "OPA" },
{ to: "/projects/regal", label: "Regal" },
{
type: "html",
value: "<hr style=\"margin: 0.3rem 1rem\">",
},
{ href: "https://open-policy-agent.github.io/gatekeeper/website/", label: "OPA Gatekeeper" },
{ href: "https://www.conftest.dev", label: "Conftest" },
],
},
{ to: "/ecosystem/", label: "Ecosystem", position: "right" },
{ href: "https://play.openpolicyagent.org/", label: "Play", position: "right" },
{
type: "html",
position: "right",
value: `
<a href="https://github.com/open-policy-agent"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub repository">
<img src="${
path.join(baseUrl, "img/nav/github-light.svg")
}" class="light-only" alt="GitHub" style="width: 24px; height: auto; margin-left: 8px;" />
<img src="${
path.join(baseUrl, "img/nav/github-dark.svg")
}" class="dark-only" alt="GitHub" style="width: 24px; height: auto; margin-left: 8px;" />
</a>
`,
},
{
type: "html",
position: "right",
value: `
<a href="https://slack.openpolicyagent.org/"
target="_blank"
rel="noopener noreferrer"
aria-label="Slack community">
<img src="${
path.join(baseUrl, "img/nav/slack-light.svg")
}" class="light-only" alt="Slack" style="width: 24px; height: auto; margin-left: 8px;" />
<img src="${
path.join(baseUrl, "img/nav/slack-dark.svg")
}" class="dark-only" alt="Slack" style="width: 24px; height: auto; margin-left: 8px;" />
</a>
`,
},
],
},
footer: {
style: "light",
links: [],
copyright:
`Open Policy Agent is a <a href="https://www.cncf.io/">Cloud Native Computing Foundation</a> Graduated project.
<img src="${
path.join(baseUrl, "img/footer/cncf-light.svg")
}" alt="CNCF Logo" class="light-only" style="max-width: 10rem; vertical-align: middle; margin: 0 10px;">
<img src="${
path.join(baseUrl, "img/footer/cncf-dark.svg")
}" alt="CNCF Logo" class="dark-only" style="max-width: 10rem; vertical-align: middle; margin: 0 10px;">
<br />
© ${new Date().getFullYear()}
Open Policy Agent contributors.
<a href="https://github.com/open-policy-agent/opa/blob/main/LICENSE">Licensed under the Apache License, Version 2.0</a>.
See the <a href="${
path.join(baseUrl, "/docs/contributing")
}">contributing documentation</a> for information about contributing.
The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
additionalLanguages: [
"rego",
"hcl",
"json",
"java",
"scala",
"gradle",
"javadoc",
"sql",
"http",
"diff",
"typescript",
"ini",
"cypher",
"csharp",
"shell-session",
"go-module",
"docker",
"javastacktrace",
"properties",
"log",
],
magicComments: [
{
className: "code-block-terminal-command",
line: "terminal-command",
},
{
className: "code-block-terminal-command",
line: "cmd",
},
{
className: "code-block-diff-add-line",
line: "diff-add",
block: { start: "diff-add-start", end: "diff-add-end" },
},
{
className: "code-block-diff-remove-line",
line: "diff-remove",
block: { start: "diff-remove-start", end: "diff-remove-end" },
},
{
className: "theme-code-block-highlighted-line",
line: "highlight-next-line",
block: { start: "highlight-start", end: "highlight-end" },
},
{
className: "code-block-error-line",
line: "error-next-line",
block: { start: "error-start", end: "error-end" },
},
],
},
mermaid: {
theme: { light: "base", dark: "dark" },
options: {
themeVariables: { // https://mermaid.js.org/config/theming.html#theme-variables
fontFamily: "sans-serif",
primaryColor: "#76d3ed",
secondaryColor: "#fff",
tertiaryColor: "#fff",
},
},
},
},
plugins: [
[
"@docusaurus/plugin-google-gtag",
{
trackingID: "G-JNBNV64PDX",
anonymizeIP: true,
},
],
[
"@docusaurus/plugin-content-docs",
{
id: "regal",
path: "projects/regal",
routeBasePath: "projects/regal",
sidebarPath: require.resolve("./src/lib/sidebar-auto.js"),
},
],
() => ({
name: "raw-loader",
configureWebpack() {
return {
module: {
rules: [
{ test: /\.rego$/, use: "raw-loader" },
{ test: /\.mermaid$/, use: "raw-loader" },
{ test: /\.txt$/, use: "raw-loader" },
],
},
};
},
}),
async function ecosystemLanguagePageGen(context, _options) {
return {
name: "ecosystem-language-gen",
async loadContent() {
const languages = await loadPages(path.join(context.siteDir, "src/data/ecosystem/languages/*.md"));
return { languages };
},
async contentLoaded({ content, actions }) {
const { languages } = content;
await Promise.all(
Object.keys(languages).map(async (language) => {
const routePath = path.join(baseUrl, `/ecosystem/by-language/${language}`);
return actions.addRoute({
path: routePath,
component: require.resolve("./src/EcosystemLanguage.jsx"),
exact: true,
modules: {},
customData: { language },
});
}),
);
},
};
},
async function ecosystemFeaturePageGen(context, _options) {
return {
name: "ecosystem-feature-gen",
async loadContent() {
const features = await loadPages(path.join(context.siteDir, "src/data/ecosystem/features/*.md"));
return { features };
},
async contentLoaded({ content, actions }) {
const { features } = content;
await Promise.all(
Object.keys(features).map(async (feature) => {
const routePath = path.join(baseUrl, `/ecosystem/by-feature/${feature}`);
return actions.addRoute({
path: routePath,
component: require.resolve("./src/EcosystemFeature.jsx"),
exact: true,
modules: {},
customData: { feature },
});
}),
);
},
};
},
async function ecosystemData(context, _options) {
return {
name: "ecosystem-data",
async loadContent() {
const entries = await loadPages(path.join(context.siteDir, "src/data/ecosystem/entries/*.md"));
const languages = await loadPages(path.join(context.siteDir, "src/data/ecosystem/languages/*.md"));
const features = await loadPages(path.join(context.siteDir, "src/data/ecosystem/features/*.md"));
const featureCategories = await loadPages(
path.join(context.siteDir, "src/data/ecosystem/feature-categories/*.md"),
);
return {
entries,
languages,
features,
featureCategories,
};
},
async contentLoaded({ content, actions }) {
const { createData } = actions;
const { entries, languages, features, featureCategories } = content;
await createData("entries.json", JSON.stringify(entries, null, 2));
await createData("languages.json", JSON.stringify(languages, null, 2));
await createData("features.json", JSON.stringify(features, null, 2));
await createData("feature-categories.json", JSON.stringify(featureCategories, null, 2));
},
};
},
async function builtinData(_context, _options) {
return {
name: "builtin-data",
async loadContent() {
const filePath = "../builtin_metadata.json";
const fileContent = await fs.readFile(filePath, "utf-8");
const builtins = JSON.parse(fileContent);
return { builtins };
},
async contentLoaded({ content, actions }) {
const { createData } = actions;
const { builtins } = content;
await createData("builtins.json", JSON.stringify(builtins, null, 2));
},
};
},
async function ecosystemPagesGen(context, _options) {
return {
name: "ecosystem-entries-pages-gen",
async loadContent() {
const entries = await loadPages(path.join(context.siteDir, "src/data/ecosystem/entries/*.md"));
return { entries };
},
async contentLoaded({ content, actions }) {
const { entries } = content;
await Promise.all(
Object.values(entries).map(async (entry) => {
const routePath = path.join(baseUrl, `/ecosystem/entry/${entry.id}`);
return actions.addRoute({
path: routePath,
component: require.resolve("./src/EcosystemEntry.jsx"),
exact: true,
modules: {},
customData: { id: entry.id },
});
}),
);
},
};
},
async function eventsData(context, _options) {
return {
name: "events-data",
async loadContent() {
const events = await loadEvents(path.join(context.siteDir, "src/data/events/*.json"));
return { events };
},
async contentLoaded({ content, actions }) {
const { createData } = actions;
const { events } = content;
await createData("events.json", JSON.stringify(events, null, 2));
},
};
},
async function eventsPagesGen(context, _options) {
return {
name: "events-pages-gen",
async loadContent() {
const events = await loadEvents(path.join(context.siteDir, "src/data/events/*.json"));
return { events };
},
async contentLoaded({ content, actions }) {
const { events } = content;
await Promise.all(
Object.values(events).map(async (event) => {
const routePath = path.join(baseUrl, `/events/${event.id}`);
return actions.addRoute({
path: routePath,
component: require.resolve("./src/EventPage.jsx"),
exact: true,
modules: {},
customData: { id: event.id },
});
}),
);
},
};
},
async function versionsData(context, _options) {
return {
name: "versions-data",
async loadContent() {
const capabilitiesDir = path.resolve(__dirname, "../capabilities");
const dirents = await fs.readdir(capabilitiesDir, { withFileTypes: true });
const versionStrings = dirents
.filter(dirent => dirent.isFile() && dirent.name.endsWith(".json"))
.map(dirent => dirent.name.replace(".json", ""));
const validVersions = versionStrings.filter(v => semver.valid(v));
const sortedVersions = semver.sort(validVersions);
return { versions: sortedVersions };
},
async contentLoaded({ content, actions }) {
const { createData } = actions;
const { versions } = content;
await createData("versions.json", JSON.stringify(versions, null, 2));
const staticDir = path.join(context.siteDir, "static", "data");
await fs.mkdir(staticDir, { recursive: true });
await fs.writeFile(
path.join(staticDir, "versions.json"),
JSON.stringify(versions, null, 2),
);
},
};
},
async function cliData(context, _options) {
return {
name: "cli-data",
async loadContent() {
const filePath = path.join(context.siteDir, "src/data/cli.json");
const cliJson = await fs.readFile(filePath, "utf-8");
const parsedData = JSON.parse(cliJson);
return parsedData;
},
async contentLoaded({ content, actions }) {
const { createData } = actions;
await createData("cli.json", JSON.stringify(content, null, 2));
},
};
},
async function versionsPageGen(_context, _options) {
return {
name: "version-page-gen",
async contentLoaded({ content: _content, actions }) {
return actions.addRoute({
path: path.join(baseUrl, `/docs/archive`),
component: require.resolve("./src/Archive.jsx"),
exact: true,
modules: {},
});
},
};
},
async function regalRulesData() {
return {
name: "regal",
async loadContent() {
const rules = await loadRules();
return { rules };
},
async contentLoaded({ content, actions }) {
await actions.createData("rules.json", JSON.stringify(content.rules, null, 2));
},
};
},
async function surveyData(context, _options) {
return {
name: "survey-data",
async loadContent() {
const eventData = await loadSurveyEventData(
path.join(context.siteDir, "src/data/surveys/events/**/**/data.json"),
);
const questions = await loadSurveyQuestions(
path.join(context.siteDir, "src/data/surveys/questions/**/data.json"),
);
const eventMetadata = await loadSurveyEventMetadata(
path.join(context.siteDir, "src/data/surveys/events/**/metadata.json"),
);
return { eventData, questions, eventMetadata };
},
async contentLoaded({ content, actions }) {
const { createData, addRoute } = actions;
const { eventData, questions, eventMetadata } = content;
await createData("survey-event-data.json", JSON.stringify(eventData, null, 2));
await createData("survey-questions.json", JSON.stringify(questions, null, 2));
await createData("survey-event-metadata.json", JSON.stringify(eventMetadata, null, 2));
// Create a page for each event
const eventSlugs = Object.keys(eventData);
await Promise.all(
eventSlugs.map(async (eventSlug) => {
const routePath = path.join(baseUrl, `/survey/${eventSlug}`);
return addRoute({
path: routePath,
component: require.resolve("./src/SurveyEvent/index.jsx"),
exact: true,
modules: {},
customData: { eventSlug },
});
}),
);
},
};
},
async function glossaryData(context) {
return {
name: "glossary-data",
async loadContent() {
const glossaryPath = path.join(context.siteDir, "glossary.yaml");
const fileContent = await fs.readFile(glossaryPath, "utf-8");
const glossary = yaml.load(fileContent);
return { glossary };
},
async contentLoaded({ content, actions }) {
await actions.createData("glossary.json", JSON.stringify(content.glossary, null, 2));
},
};
},
async function irPlanSchema(context) {
return {
name: "ir-plan-schema",
async loadContent() {
const sourcePath = path.resolve(__dirname, "../v1/ir/plan.schema.json");
const targetDir = path.join(context.siteDir, "static/schemas/ir/v1");
await fs.mkdir(targetDir, { recursive: true });
await fs.copyFile(sourcePath, path.join(targetDir, "plan.schema.json"));
},
};
},
async function bundleManifestSchema(context) {
return {
name: "bundle-manifest-schema",
async loadContent() {
const sourcePath = path.resolve(__dirname, "../v1/bundle/manifest.schema.json");
const targetDir = path.join(context.siteDir, "static/schemas/bundle/v1");
await fs.mkdir(targetDir, { recursive: true });
await fs.copyFile(sourcePath, path.join(targetDir, "manifest.schema.json"));
},
};
},
markdownExportPlugin,
],
clientModules: [
require.resolve("./src/lib/gtagFallback.js"),
require.resolve("./src/lib/playground.js"),
require.resolve("./src/lib/kapa.js"),
],
stylesheets: [
{
href: "https://unpkg.com/@antonz/codapi@0.19.8/dist/snippet.css",
},
],
scripts: [
{
src: "https://unpkg.com/@antonz/codapi@0.19.8/dist/snippet.js",
defer: true,
},
{
src: "https://widget.kapa.ai/kapa-widget.bundle.js",
"data-website-id": "5c4af137-a024-4ba2-9488-826f1055852c",
"data-project-name": "Open Policy Agent",
"data-project-logo": "https://openpolicyagent.org/img/nav/logo.png",
"data-search-mode-enabled": "true",
// Source UUIDs come from https://app.kapa.ai/422cffb8-2750-48c9-9659-7e8d40b9d5d2/sources
"data-search-source-ids-include":
"f9c26922-8a4c-4a3c-913d-08c2a53a1011,e9daf266-5a9f-410f-8069-18a57dcfb6ee,723b2ac1-807a-43c1-a2aa-bcf8311d2354,fff41a96-7ebe-4fc5-bb60-359e5d830a87,799aac5c-a157-4734-9785-9c754c7c40a0",
"data-modal-open-on-command-k": "true",
"data-modal-command-k-search-mode-default": "true",
"data-deep-thinking-button-hover-background-color": "#f1f8f1",
"data-deep-thinking-button-hover-color": "#2e7d32",
"data-deep-thinking-button-enabled-background-color": "#e8f5e9",
"data-deep-thinking-button-enabled-color": "#2e7d32",
"data-deep-thinking-button-enabled-hover-background-color": "#c8e6c9",
"data-deep-thinking-button-enabled-hover-color": "#1b5e20",
"data-deep-thinking-button-hover-background-color-dark": "#1e3320",
"data-deep-thinking-button-hover-color-dark": "#81c784",
"data-deep-thinking-button-enabled-background-color-dark": "#1a3d1c",
"data-deep-thinking-button-enabled-color-dark": "#a5d6a7",
"data-deep-thinking-button-enabled-hover-background-color-dark": "#245427",
"data-deep-thinking-button-enabled-hover-color-dark": "#c8e6c9",
"data-modal-title": "OPA Chat and Search",
"data-color-scheme-selector": "[data-theme='dark']",
async: true,
},
],
}
);