Skip to content

getplumber/getplumber.io

Repository files navigation

Plumber - Website

Plumber logo

A modern website for Plumber (open-source CI/CD compliance CLI), built with Astro, Tailwind CSS, and React.

Summary of this README

Section
Summary
Quick Start
Architecture & Technology Stack
Project Structure
Creating Content
Adding a new release blog post
Creating Documentation Articles
Creating Custom Pages
Component Patterns
Styling & Theming
Search Configuration
Internationalization (i18n)
Content Management (Keystatic CMS)
Deployment
Development Tips
Content Collections API
Configuration Files
Testing Locally
Additional Resources
Contributing
License
Support

πŸ“‹ Summary

This is a comprehensive documentation and website for Plumber - a tool for analyzing GitLab CI/CD pipelines for security and compliance issues. Plumber helps you:

  • Map out complex CI/CD landscapes
  • Detect CI/CD security issues
  • Ensure supply chain compliance
  • Spot CI/CD technical debt
  • Avoid supply chain attacks

The website features:

  • πŸ“ Blog - Release notes, tutorials, and articles about CI/CD security
  • πŸ“š Documentation - Comprehensive guides for installation, usage, and reference
  • 🎨 Marketing Pages - Product landing pages and feature showcases
  • πŸ” Search - Full-text search powered by Pagefind
  • ✨ CMS - Keystatic CMS for content management

πŸš€ Quick Start

Prerequisites

  • Node.js 20+
  • npm

Installation & Setup

  1. Install dependencies:

    npm install
  2. Run initial build:

    npm run build
  3. Copy Pagefind for search:

    • Windows: npm run winsearch
    • macOS/Linux: npm run osxsearch
  4. Configure i18n:

    npm run config-i18n

    Follow the prompts to set up your language configuration.

  5. Start development server:

    npm run dev

    Visit http://localhost:4321

Available Commands

Command Action
npm run dev Start local dev server at localhost:4321
npm run build Build production site to ./dist/ with search indexing
npm run preview Preview production build (not supported with Vercel adapter)
npm run format Format code with ESLint and Prettier
npm run lint Run ESLint
npm run config-i18n Configure internationalization
npm run remove-keystatic Remove Keystatic CMS if not needed
npm run seo-audit Run SEO audit on the site
npm run generate-favicon-ico Generate favicon.ico from favicon.svg

πŸ—οΈ Architecture & Technology Stack

Core Technologies

Key Features & Tools

  • Keystatic CMS - Git-based CMS for content editing (accessible at /keystatic or /admin)
  • Pagefind - Fast static search
  • Astro SEO - SEO optimization
  • Astro Icon - Icon system using Tabler icons
  • Motion on Scroll - Scroll-triggered animations

Build & Deployment

  • Deployment: Vercel (via @astrojs/vercel adapter)
  • Image Optimization: Sharp image service
  • Compression: HTML/JS compression via Playform Compress
  • RSS Feed: Automatic RSS generation for blog posts

πŸ“ Project Structure

.
β”œβ”€β”€ .github/
β”‚   └── workflows/          # CI/CD (lint, preview, production, security, seo-audit)
β”œβ”€β”€ public/                 # Static assets
β”‚   β”œβ”€β”€ favicons/
β”‚   β”œβ”€β”€ robots.txt
β”‚   └── social-media-card.png
β”œβ”€β”€ scripts/                # Utility scripts
β”‚   β”œβ”€β”€ config-i18n.js     # i18n configuration wizard
β”‚   β”œβ”€β”€ generate-favicon-ico.js
β”‚   β”œβ”€β”€ i18n/              # i18n management utilities
β”‚   β”œβ”€β”€ remove-keystatic.js
β”‚   └── seo-audit.js
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ assets/            # Images, videos (optimized by Astro)
β”‚   β”œβ”€β”€ components/        # Reusable Astro/React components
β”‚   β”‚   β”œβ”€β”€ Hero/          # Hero sections
β”‚   β”‚   β”œβ”€β”€ Feature/       # Feature sections
β”‚   β”‚   β”œβ”€β”€ Cta/           # Call-to-action components
β”‚   β”‚   β”œβ”€β”€ Nav/           # Navigation
β”‚   β”‚   β”œβ”€β”€ Footer/
β”‚   β”‚   └── ...
β”‚   β”œβ”€β”€ config/            # Site configuration
β”‚   β”‚   β”œβ”€β”€ en/            # Locale-specific (siteData, navData, faqData, etc.)
β”‚   β”‚   β”œβ”€β”€ siteSettings.json.ts
β”‚   β”‚   └── translationData.json.ts
β”‚   β”œβ”€β”€ data/              # Content collections data
β”‚   β”‚   β”œβ”€β”€ blog/          # Blog posts (by language)
β”‚   β”‚   β”‚   └── en/
β”‚   β”‚   β”œβ”€β”€ authors/       # Author profiles
β”‚   β”‚   β”œβ”€β”€ otherPages/    # Additional pages (privacy, terms)
β”‚   β”‚   └── codeToggles/   # Code example toggles
β”‚   β”œβ”€β”€ docs/              # Documentation system
β”‚   β”‚   β”œβ”€β”€ components/    # Docs-specific components
β”‚   β”‚   β”‚   └── mdx-components/  # MDX components (Aside, Badge, Tabs, etc.)
β”‚   β”‚   β”œβ”€β”€ config/        # Docs configuration
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   └── docs/      # Documentation content (by language)
β”‚   β”‚   β”‚       └── en/
β”‚   β”‚   β”œβ”€β”€ layouts/       # Docs layouts
β”‚   β”‚   └── styles/        # Docs-specific styles
β”‚   β”œβ”€β”€ icons/             # SVG icons
β”‚   β”‚   β”œβ”€β”€ logos/
β”‚   β”‚   └── tabler/        # Tabler icons
β”‚   β”œβ”€β”€ js/                # Utility functions
β”‚   β”‚   β”œβ”€β”€ localeUtils.ts
β”‚   β”‚   └── translationUtils.ts
β”‚   β”œβ”€β”€ layouts/           # Page layouts
β”‚   β”‚   β”œβ”€β”€ BaseLayout.astro
β”‚   β”‚   β”œβ”€β”€ BaseHead.astro
β”‚   β”‚   └── Blog*.astro    # Blog layouts
β”‚   β”œβ”€β”€ pages/             # Route-based pages
β”‚   β”‚   β”œβ”€β”€ index.astro    # Homepage
β”‚   β”‚   β”œβ”€β”€ platform.astro # Platform product page
β”‚   β”‚   β”œβ”€β”€ blog/
β”‚   β”‚   β”‚   β”œβ”€β”€ index.astro       # Blog list
β”‚   β”‚   β”‚   └── [...slug].astro   # Blog post
β”‚   β”‚   β”œβ”€β”€ docs/
β”‚   β”‚   β”‚   β”œβ”€β”€ index.astro       # Docs home
β”‚   β”‚   β”‚   β”œβ”€β”€ [docsRoute]/index.astro
β”‚   β”‚   β”‚   └── [...slug].astro   # Docs pages
β”‚   β”‚   β”œβ”€β”€ categories/    # Category pages
β”‚   β”‚   β”œβ”€β”€ contact.astro
β”‚   β”‚   β”œβ”€β”€ [...page].astro # Other pages (privacy-policy, terms-of-use)
β”‚   β”‚   β”œβ”€β”€ 404.astro
β”‚   β”‚   └── rss.xml.ts     # RSS feed
β”‚   β”œβ”€β”€ styles/            # Global styles
β”‚   β”‚   β”œβ”€β”€ global.css
β”‚   β”‚   β”œβ”€β”€ tailwind-theme.css
β”‚   β”‚   β”œβ”€β”€ buttons.css
β”‚   β”‚   β”œβ”€β”€ markdown-content.css
β”‚   β”‚   β”œβ”€β”€ fonts.css
β”‚   β”‚   β”œβ”€β”€ mos.css
β”‚   β”‚   └── keystatic.css
β”‚   └── content.config.ts  # Content collections schema
β”œβ”€β”€ astro.config.mjs       # Astro configuration
β”œβ”€β”€ keystatic.config.tsx   # Keystatic CMS configuration
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
└── vercel.json            # Vercel deployment config

✍️ Creating Content

Creating Blog Articles

Blog articles use MDX format and are stored in src/data/blog/[locale]/.

1. File Structure

Create a folder with your article slug:

src/data/blog/en/my-article-name/
β”œβ”€β”€ index.mdx          # Article content
β”œβ”€β”€ heroImage.png      # Hero image (required)
└── other-images.png   # Additional images (optional)

2. Article Frontmatter

Every blog article must have frontmatter at the top:

---
title: Your Article Title
description: A compelling description for SEO and previews
draft: false
authors:
  - main-author
  - second-author
pubDate: 2024-01-15
updatedDate: 2024-01-20 # Optional
heroImage: ./heroImage.png
categories:
  - ci-cd
  - security
mappingKey: my-article # Optional: for i18n matching
---

Your article content starts here...

## Headings

Write in Markdown/MDX format.

![Alt text](./other-image.png)

3. Frontmatter Fields

Field Type Required Description
title string βœ… Article title
description string βœ… SEO description & preview text
draft boolean ❌ Set to true to exclude from build
authors array βœ… Array of author IDs (references src/data/authors/)
pubDate date βœ… Publication date (YYYY-MM-DD)
updatedDate date ❌ Last update date
heroImage image βœ… Path to hero image
categories array ❌ Array of category strings
mappingKey string ❌ For linking translations

4. Adding Images

  • Hero images: Place in the article folder, reference as ./heroImage.png
  • Inline images: Place in the article folder, reference with relative paths
  • Optimized: All images are automatically optimized by Astro

5. Using Components in Blog Posts

Auto-imported components (no import needed):

  • <Admonition> - Info boxes

For other components, import them:

---
title: My Article
---

import CustomComponent from "@components/CustomComponent.astro";

<CustomComponent prop="value" />

6. Adding a new release blog post

To announce a new product release (e.g. 1.0.0 Plumber), add a post under the releases/ directory. These posts appear on the main blog page and in the RSS feed.

Where to add it: src/data/blog/[locale]/releases/[version]/

Example for release "1.0.0 Plumber":

src/data/blog/en/releases/1.0.0/
β”œβ”€β”€ index.mdx          # Release notes content
β”œβ”€β”€ heroImage.png      # Hero image (required)
└── screenshot.png     # Optional screenshots

Use the same frontmatter as any blog article. Include a Releases category so it’s easy to filter:

---
title: 1.0.0 Plumber Release
description: Short description for SEO and previews
draft: false
authors:
  - plumber
pubDate: 2025-02-04
heroImage: ./heroImage.png
categories:
  - "Releases"
---

Note: Posts in archive/ are archived: they only appear on /blog/archive, not on the main blog list. Use releases/ for new release announcements and keep archive/ for old/historical release notes (e.g. legacy R2Devops releases).

7. Examples

See existing blog posts in src/data/blog/en/ for reference:

  • archive/2.17/index.mdx - Archived release notes (R2Devops)
  • releases/ - Add new Plumber release posts here
  • tj-actions-compromised/index.mdx - Article example
  • top-5-cybersecurity-incidents-in-cicd/index.mdx - Long-form article

Creating Authors

Authors are defined in src/data/authors/[author-id]/.

Structure:

src/data/authors/john-doe/
β”œβ”€β”€ index.mdx
└── avatar.jpg

Content (index.mdx):

---
name: John Doe
avatar: ./avatar.jpg
about: DevOps engineer passionate about CI/CD security
email: john@example.com
authorLink: https://johndoe.com
---

πŸ“š Creating Documentation Articles

Documentation articles use MDX and are stored in src/docs/data/docs/[locale]/.

1. File Structure

Option A: Single file

src/docs/data/docs/en/getting-started.mdx

Option B: Folder with index (recommended for images)

src/docs/data/docs/en/installation/
β”œβ”€β”€ index.mdx
└── img/
    └── screenshot.png

Option C: Nested sections

src/docs/data/docs/en/installation/
β”œβ”€β”€ index.mdx
β”œβ”€β”€ docker-compose.mdx
β”œβ”€β”€ kubernetes.mdx
└── img/
    └── diagrams.svg

2. Documentation Frontmatter

---
title: Getting Started with Plumber
description: Learn how to install and use Plumber CLI
sidebar:
  label: Quick Start # Label in sidebar (defaults to title)
  order: 1 # Order in sidebar
  badge:
    text: New
    variant: tip # note, tip, caution, danger, info
tableOfContents:
  minHeadingLevel: 2
  maxHeadingLevel: 3
pagefind: true # Include in search (default: true)
draft: false # Exclude from build if true
mappingKey: getting-started # For i18n
---

## Your documentation content

Write your docs here in Markdown/MDX.

3. Frontmatter Fields

Field Type Required Description
title string βœ… Page title
description string ❌ SEO description
sidebar.label string ❌ Sidebar text (defaults to title)
sidebar.order number ❌ Sidebar position
sidebar.badge object ❌ Badge with text & variant
tableOfContents object ❌ TOC configuration
pagefind boolean ❌ Include in search (default: true)
draft boolean ❌ Exclude from build
mappingKey string ❌ For i18n matching

4. Using MDX Components in Docs

The following components are auto-imported in all documentation files:

Aside / Admonition
<Aside variant="note">This is a note callout.</Aside>

<Aside variant="tip">This is a helpful tip!</Aside>

<Aside variant="caution">Be careful with this.</Aside>

<Aside variant="danger">This is dangerous!</Aside>

Variants: note, tip, caution, danger, info

Badge
<Badge variant="tip">New</Badge>
<Badge variant="caution">Deprecated</Badge>
Button
<Button href="/docs/installation">Get Started</Button>
<Button variant="secondary">Learn More</Button>
Steps
<Steps>
1. First, install the dependencies
   ```bash
   npm install
  1. Then configure your settings
  2. Finally, run the build

##### Tabs

```mdx
<Tabs defaultValue="npm">
  <TabsList>
    <TabsTrigger value="npm">npm</TabsTrigger>
    <TabsTrigger value="pnpm">pnpm</TabsTrigger>
  </TabsList>

  <TabsContent value="npm">
    ```bash
    npm install plumber-cli
    ```
  </TabsContent>

  <TabsContent value="pnpm">
    ```bash
    pnpm add plumber-cli
    ```
  </TabsContent>
</Tabs>

5. Documentation Navigation

The sidebar is auto-generated from:

  1. The folder structure in src/docs/data/docs/[locale]/
  2. The sidebar.order values in frontmatter
  3. The sidebar.label values (or falls back to title)

Tips:

  • Use sidebar.order to control positioning
  • Folders become collapsible sections
  • index.mdx files become the section's main page

6. Examples

See existing docs for reference:

  • src/docs/data/docs/en/getting-started/index.mdx - Overview
  • src/docs/data/docs/en/installation/ - Multiple pages (docker-compose, kubernetes, podman, etc.)
  • src/docs/data/docs/en/components/ - MDX components (Aside, Badge, Button, Steps, Tabs)
  • src/docs/data/docs/en/use-plumber/ - Controls, issues, register-templates, roles-permissions

🎨 Creating Custom Pages

Creating a Static Page

Create a new .astro file in src/pages/:

---
// src/pages/about.astro
import BaseLayout from "@layouts/BaseLayout.astro";
import Hero1 from "@components/Hero/Hero1.astro";
---

<BaseLayout title="About Us" description="Learn about our mission">
  <Hero1 />

  <section class="container mx-auto px-4 py-16">
    <h2>Our Story</h2>
    <p>Content here...</p>
  </section>
</BaseLayout>

This creates a route at /about.

Creating Dynamic Pages

Use [param].astro for dynamic routes:

---
// src/pages/product/[id].astro
export async function getStaticPaths() {
  return [{ params: { id: "plumber" } }, { params: { id: "platform" } }];
}

const { id } = Astro.params;
---

<BaseLayout title={`Product: ${id}`}>
  <!-- Content -->
</BaseLayout>

🧩 Component Patterns

Using Existing Components

The project includes many pre-built components:

---
import Hero1 from "@components/Hero/Hero1.astro";
import Feature1 from "@components/Feature/Feature1.astro";
import Cta1 from "@components/Cta/Cta1.astro";
import Pricing1 from "@components/Pricing/Pricing1.astro";
---

<Hero1 />
<Feature1 />
<Pricing1 />
<Cta1 />

Component Categories

  • Hero - Hero sections for landing pages
  • Feature - Feature showcase sections
  • Cta - Call-to-action sections
  • Pricing - Pricing tables
  • Testimonials - Customer testimonials
  • LogoCloud - Client/partner logos
  • PostCard - Blog post cards
  • SiteLogo - Site logo (nav, footer, mobile)
  • Nav / Footer - Navigation and footer

Creating Custom Components

---
// src/components/MyComponent/MyComponent.astro
interface Props {
  title: string;
  description?: string;
}

const { title, description } = Astro.props;
---

<div class="my-component">
  <h2>{title}</h2>
  {description && <p>{description}</p>}
  <slot />
</div>

<style>
  .my-component {
    /* Component-scoped styles */
  }
</style>

Usage:

---
import MyComponent from "@components/MyComponent/MyComponent.astro";
---

<MyComponent title="Hello" description="World">
  <p>Slotted content</p>
</MyComponent>

🎨 Styling & Theming

Tailwind Configuration

Tailwind 4 is configured via CSS variables in src/styles/tailwind-theme.css:

@theme {
  --color-primary-*: ...;
  --color-secondary-*: ...;
}

Global Styles

Located in src/styles/:

  • global.css - Base styles, CSS variables
  • buttons.css - Button variants
  • markdown-content.css - Markdown rendering styles
  • fonts.css - Font imports

Dark Mode

Dark mode is implemented via CSS classes. Toggle component: ThemeToggle.astro


πŸ” Search Configuration

Search is powered by Pagefind, built during the production build.

Configuration:

  • Automatically indexes all pages
  • Control indexing with data-pagefind-ignore attribute
  • Control in docs with pagefind: false in frontmatter

Building search index:

npm run build  # Runs pagefind and copies index into Vercel output for deployed search

🌐 Internationalization (i18n)

Current Setup

  • Default language: English (en)
  • Locale prefix: Disabled for default locale

Adding a New Language

  1. Run the i18n configuration wizard:

    npm run config-i18n
  2. Follow the prompts to add a new locale

  3. The script will:

    • Update astro.config.mjs
    • Update keystatic.config.tsx
    • Update site settings
    • Create content folders for the new locale

Manual Translation

After adding a locale, translate content by:

  1. Copying content from src/data/blog/en/ to src/data/blog/[locale]/
  2. Copying docs from src/docs/data/docs/en/ to src/docs/data/docs/[locale]/
  3. Updating src/config/translationData.json.ts

πŸ“ Content Management (Keystatic CMS)

Accessing Keystatic

  • Local development: http://localhost:4321/keystatic
  • Production: https://yoursite.com/keystatic

Configuration

Keystatic is configured in keystatic.config.tsx. Current collections:

  • blogEN - Blog posts (English)
  • authors - Author profiles
  • otherPagesEN - Additional pages

Authentication

  • Local: No authentication required
  • Production: Uses Keystatic Cloud (requires account)

Removing Keystatic

If you don't need a CMS:

npm run remove-keystatic

πŸš€ Deployment

Vercel (Recommended)

The project is configured for Vercel deployment:

  1. Connect your repository to Vercel
  2. Vercel auto-detects Astro
  3. Build command: npm run build
  4. Output directory: dist

Configuration is in vercel.json and uses the @astrojs/vercel adapter.

Other Platforms

To deploy on other platforms:

  1. Update the adapter in astro.config.mjs
  2. Follow Astro's deployment guides

Environment Variables

For Keystatic Cloud in production:


πŸ› οΈ Development Tips

Hot Module Replacement (HMR)

Astro provides fast HMR for:

  • Astro components
  • MDX files
  • Styles
  • React components

TypeScript

TypeScript is fully configured. Run type checking:

npx astro check

Linting & Formatting

npm run lint      # Check for issues
npm run format    # Auto-fix issues

Code Tours

Install the CodeTour extension to view guided tours in .tours/.


πŸ“¦ Content Collections API

Querying Blog Posts

---
import { getCollection } from "astro:content";

// Get all blog posts
const allPosts = await getCollection("blog");

// Filter published posts
const publishedPosts = await getCollection("blog", ({ data }) => {
  return !data.draft;
});

// Get by category
const securityPosts = allPosts.filter((post) => post.data.categories?.includes("security"));
---

Querying Docs

---
import { getCollection } from "astro:content";

const docs = await getCollection("docs");
---

Rendering Content

---
import { getEntry } from "astro:content";

const post = await getEntry("blog", "my-article-name");
const { Content } = await post.render();
---

<article>
  <h1>{post.data.title}</h1>
  <Content />
</article>

πŸ”§ Configuration Files

astro.config.mjs

Main Astro configuration:

  • Site URL, image service
  • Integrations (MDX, React, Sitemap, Keystatic, Compress, etc.)
  • Markdown settings (Shiki syntax highlighting)
  • i18n configuration
  • Auto-imports for MDX

content.config.ts

Content collections schema:

  • Defines validation for frontmatter (blog, authors, docs, otherPages)
  • Configures content loaders
  • Sets up relationships (e.g. blog posts β†’ authors)

package.json

Dependencies and scripts. Key packages:

  • Astro 5.x
  • Tailwind CSS 4.x
  • React 19.x
  • Keystatic CMS
  • Pagefind (search)
  • Node 20+

tsconfig.json

TypeScript configuration with path aliases:

  • @components/* β†’ src/components/*
  • @layouts/* β†’ src/layouts/*
  • @js/* β†’ src/js/*
  • etc.

πŸ§ͺ Testing Locally

Full Production Build

npm run build

Note: With the Vercel adapter, npm run preview is not supported. To test the built site locally, use a static server (e.g. npx serve dist/client -p 4321) or deploy to a Vercel preview.

Test Search

Search only works after running a production build:

npm run build
npm run winsearch  # or osxsearch
npm run dev

πŸ“š Additional Resources

Plumber

  • Plumber CLI: Open-source GitLab CI/CD compliance tool
  • Plumber Platform: Full-featured compliance and security platform
  • Discord Community: Join for support and discussions
  • Support Email: hello@getplumber.io

Framework Documentation

Related Tools


🀝 Contributing

When contributing content:

  1. Follow the established patterns in existing content
  2. Use the frontmatter schemas defined in content.config.ts
  3. Test locally before committing
  4. Ensure all images are optimized
  5. Run linting: npm run format

πŸ“„ License

See license details at the main Plumber project repository.


πŸ†˜ Support

  • Technical Issues: Open an issue in the repository
  • Content Questions: Contact via Discord or support email
  • Theme/Styling: Refer to Cosmic Themes documentation (original theme base)

Built with ❀️ by the Plumber team

Securing CI/CD pipelines, one commit at a time.