The source for brightdigit.com — a static site built entirely in Swift. It generates roughly 450 HTML pages from Markdown: 54 articles, 45 tutorials, 210 podcast episodes, 118 newsletter issues, and 15 product pages, plus RSS feeds and a sitemap.
The purpose of vendoring the entire dependency graph into this repo as git-subrepos was a temporary strategy enabling simultaneous development, migration, and upgrade of all 20 first-party packages to Swift 6.4 with strict concurrency in a single unified environment. Once fully modernized, each package was squashed, tagged, and released to its own repository in dependency order. Today Package.swift consumes all 20 first-party packages as ordinary version pins, like any standard SwiftPM project.
This README explains how the site works, how to develop on it, and where the project stands.
- How the site works
- Content automation & Contribute framework
- The Swift package ecosystem
- External services
- Working on the site
- Project status
- Historical PRs & Key Issues
Everything is driven by a single executable, brightdigitwg (Sources/brightdigitwg/BrightDigitWG.swift is a four-line shim). Commands are dispatched by a hand-rolled CommandDispatcher built on ConfigKeyKit, using declarative ConfigKey types layered over apple/swift-configuration so options can come from CLI arguments or environment variables with an explicit precedence contract. There is no default command; dispatch greedily matches the longest registered name:
| Command | What it does |
|---|---|
publish --mode <drafts|production> |
Generate the site into Output/ |
import podcast |
Merge the Transistor RSS feed with YouTube video metadata into episode Markdown |
import mailchimp |
Import newsletter campaigns from Mailchimp (legacy platform) |
import buttondown |
Import newsletter emails from Buttondown (current platform) |
import wordpress |
One-time WordPress-export migration (articles/tutorials) |
buttondown reconcile |
Cross-check Mailchimp campaigns against the Buttondown archive; update-only; requires --preview-directory or --execute |
url podcast |
Print episode URLs |
The site definition lives in Sources/BrightDigitSite/BrightDigitSite.swift as a Publish pipeline:
- Copy
Resources/(favicons, media,_redirects) intoOutput/ - Install the Transistor and YouTube plugins (they expand shortcodes in Markdown into embedded players)
- Parse all Markdown in
Content/ - In
productionmode, drop future-dated items (item.date > now) — this is how posts get scheduled;draftsmode keeps everything - Sort by date, generate HTML with the custom theme, emit RSS (
/feed.rss,/articles.rss,/tutorials.rss) and a sitemap - Run the npm step:
npm ci && npm run publishinsideStyling/, which webpack-bundles all CSS and JS into a singlejs/main.js
Content/ holds Markdown files organized into five section directories. Each file contains strongly-typed YAML front matter decoded by ItemMetadata in BrightDigitSite.swift:
| Section | Directory | Creation Source | Key Front-Matter Fields | Purpose |
|---|---|---|---|---|
| Articles | articles/ |
Manual (Hand-written) | featuredImage, technologies |
Technical blog posts & Swift articles |
| Tutorials | tutorials/ |
Manual (Hand-written) | featuredImage, technologies |
In-depth developer tutorials |
| Episodes | episodes/ |
Machine (import podcast bot) |
youtubeID, audioDuration, podcastID |
EmpowerApps.Show podcast episodes |
| Newsletters | newsletters/ |
Machine (import buttondown / import mailchimp) |
issueNo, featuredImage |
Archived newsletter issues |
| Products | products/ |
Manual (Hand-written) | screenshots, featuredImage |
BrightDigit apps & products showcase |
Note
Top-level pages (index.md, about-us.md, services.md, contact-us.md) are lightweight stubs. Their real UI copy lives in Swift at Sources/BrightDigitSite/Strings.swift, allowing full type safety and component composition.
HTML is generated with Plot, John Sundell's type-safe HTML DSL, through a custom HTMLFactory (PiHTMLFactory.swift) and a tree of Swift components under Sources/BrightDigitSite/Components/ and Nodes/. The theme utilizes Plot's type-safe Component API, ensuring compile-time safety across all generated pages.
CSS classes in Swift go through TailwindKit, a type-safe Tailwind v4 class builder — Div().tailwind(.flex, .justify(.center)) instead of stringly-typed class lists. House rule: TailwindKit models only officially documented Tailwind v4 utilities, nothing custom.
Tailwind CSS v4 in CSS-first mode — there is no tailwind.config.js. Configuration lives in @theme blocks in Styling/styles/styles.css, which also carries @apply component styles. Syntax highlighting is client-side highlight.js, plus Mermaid for diagrams; webpack bundles everything, with CSS injected by style-loader.
CI runs import mailchimp, import podcast, and import buttondown on a six-hour cron (defined in .github/workflows/main.yaml). New episodes and newsletters are committed as bot commits — the workflow checkout uses a dedicated SSH deploy key (CONTENT_DEPLOY_KEY) rather than GITHUB_TOKEN, specifically so the bot's push does trigger the CI pipeline and the site redeploys itself. Publishing a podcast episode or newsletter therefore requires zero manual work on the website: it appears on the next cron tick.
flowchart LR
A[Cron Trigger / Workflow] --> B[brightdigitwg CLI]
B --> C[import mailchimp / import podcast / import buttondown]
C --> D[Generate Content/*.md]
D --> E[Git Commit & Push via SSH Key]
E --> F[Trigger CI Deploy Pipeline]
Content importation is powered by Contribute, a modular framework designed for ingesting external payloads and producing standard Markdown files with front-matter metadata.
flowchart TD
subgraph External APIs & Services
MC[Mailchimp API]
BD[Buttondown API]
YT[YouTube Data API v3]
RSS[RSS / Atom Feeds]
WP[WordPress Export XML]
end
subgraph Underlying Swift API Clients
ST[Spinetail Client]
BDK[ButtondownKit Client]
YTC[SwiftTube Client]
SYN[SyndiKit Parser]
end
subgraph Contribute Importer Suite
C_MC[ContributeMailchimp]
C_BD[ContributeButtondown]
C_YT[ContributeYouTube]
C_RSS[ContributeRSS]
C_WP[ContributeWordPress]
end
subgraph Contribute Core Engine
C_CORE[Contribute Core: Source -> FrontMatterTranslator -> MarkdownExtractor]
end
subgraph Output
MD[Content/*.md Files]
end
MC --> ST --> C_MC
BD --> BDK --> C_BD
YT --> YTC --> C_YT
RSS --> SYN --> C_RSS
WP --> C_WP
C_MC --> C_CORE
C_BD --> C_CORE
C_YT --> C_CORE
C_RSS --> C_CORE
C_WP --> C_CORE
C_CORE --> MD
The architecture consists of three core abstractions:
Source: Fetches raw data from external endpoints via dedicated API client packages.FrontMatterTranslator: Maps raw API response objects into strongly-typed YAML front-matter metadata (ItemMetadata).MarkdownExtractor: Generates the Markdown body content and formats the final.mdfile written intoContent/.
The importer suite pairs Contribute with dedicated Swift API client packages:
- Contribute: Core abstractions, protocols, and Markdown file writing utilities.
-
ContributeMailchimp
$\rightarrow$ powered by Spinetail (OpenAPI Mailchimp client). -
ContributeButtondown
$\rightarrow$ powered by ButtondownKit (OpenAPI Buttondown client). -
ContributeYouTube
$\rightarrow$ powered by SwiftTube (OpenAPI YouTube Data API client). -
ContributeRSS
$\rightarrow$ powered by SyndiKit (RSS/Atom/JSON-feed decoder). -
ContributeWordPress
$\rightarrow$ WordPress export XML translation.
The static site generator relies on an ecosystem of 20 first-party packages maintained under github.com/brightdigit.
graph TD
subgraph App & CLI Layer
WG[brightdigitwg Executable]
Args[BrightDigitArgs]
Site[BrightDigitSite]
Pod[BrightDigitPodcast]
end
subgraph Publish Ecosystem Forks
Pub[Publish]
Plot[Plot]
Ink[Ink]
Files[Files]
end
subgraph Site Extensions & Utilities
PubType[PublishType]
TK[TailwindKit]
CKK[ConfigKeyKit]
end
subgraph Contribute Suite
Contrib[Contribute]
C_MC[ContributeMailchimp]
C_BD[ContributeButtondown]
C_YT[ContributeYouTube]
C_RSS[ContributeRSS]
C_WP[ContributeWordPress]
end
subgraph API Clients & Parsing
ST[Spinetail]
BDK[ButtondownKit]
YT[SwiftTube]
Syn[SyndiKit]
end
subgraph Publish Plugins
P_YT[YoutubePublishPlugin]
P_TR[TransistorPublishPlugin]
P_RT[ReadingTimePublishPlugin]
P_NPM[NPMPublishPlugin]
end
WG --> Args
Args --> Site
Args --> Pod
Site --> Pub
Site --> PubType
Site --> TK
Site --> P_YT
Site --> P_TR
Site --> P_RT
Site --> P_NPM
Pod --> Contrib
Pod --> C_YT
Pod --> C_RSS
Pod --> Syn
Pub --> Plot
Pub --> Ink
Pub --> Files
Ink --> |swift-markdown| SM[swift-markdown]
Contrib --> C_MC
Contrib --> C_BD
Contrib --> C_YT
Contrib --> C_RSS
Contrib --> C_WP
C_MC --> ST
C_BD --> BDK
C_YT --> YT
C_RSS --> Syn
Args --> CKK
- Ink Migration: Ink's original hand-written parser was replaced with swift-markdown under the hood, while preserving Ink's HTML emitter and public API. Call sites resolve any
Markdownsymbol collision via the Swift 6.4 module selector (Module::Symbol). - ShellOut Migration:
NPMPublishPluginwas updated to removeShellOutin favor ofswift-subprocessfor executing Node tasks during the styling build. - Strict Concurrency: All 20 first-party packages run under Swift 6.4 complete strict concurrency (
-strict-concurrency=complete) with zero@unchecked Sendableannotations. - Files: Modernized and jumped to
5.0.0-alpha.1with native Windows path handling. - TailwindKit: Fully decoupled from Foundation and Plot using a lightweight
TailwindClassAttributeprotocol seam.
| Package | Role | Description |
|---|---|---|
| Publish | Static Site Engine | Forked and modernized to Swift 6.4 strict concurrency |
| Plot | HTML DSL | Type-safe HTML generation with Component API support |
| Ink | Markdown Parser | Powered by swift-markdown under the hood |
| Files | File System | Cross-platform file handling |
| PublishType | Publish Abstractions | Type-safe section and page builders for Publish |
| TailwindKit | Styling DSL | Type-safe Tailwind v4 class builder |
| Package | Role | Description |
|---|---|---|
| Contribute | Importer Core | Core data transformation and markdown generation engine |
| ContributeMailchimp | Importer | Mailchimp newsletter campaign ingestion (via Spinetail) |
| ContributeButtondown | Importer | Buttondown email newsletter ingestion (via ButtondownKit) |
| ContributeYouTube | Importer | YouTube video metadata processing (via SwiftTube) |
| ContributeRSS | Importer | RSS feed item extraction (via SyndiKit) |
| ContributeWordPress | Importer | WordPress export post/page translation |
| Package | Role | Description |
|---|---|---|
| ButtondownKit | API Client | OpenAPI-generated Buttondown API client |
| Spinetail | API Client | OpenAPI-generated Mailchimp API client |
| SwiftTube | API Client | OpenAPI-generated YouTube Data API v3 client |
| SyndiKit | Feed Parser | RSS, Atom, and JSON feed decoding |
| Package | Role | Description |
|---|---|---|
| YoutubePublishPlugin | Plugin | Expands YouTube shortcodes into embedded video players |
| TransistorPublishPlugin | Plugin | Expands Transistor shortcodes into embedded podcast players |
| ReadingTimePublishPlugin | Plugin | Calculates article and tutorial reading times |
| NPMPublishPlugin | Plugin | Executes Node Webpack styling build via swift-subprocess |
| Package | Role | Description |
|---|---|---|
| ConfigKeyKit | CLI / Config | Declarative ConfigKey CLI and environment configuration layer |
| Service | Used for | Location in Codebase |
|---|---|---|
| Netlify | Hosting, deploys (CLI from CI), contact form (data-netlify), redirect rules |
netlify.toml, deploy job |
| Buttondown | Newsletter: subscribe form endpoint, public archive, RSS | Strings.swift, subscription components |
| Mailchimp | Legacy newsletter platform; import source for historical archive | import mailchimp |
| Transistor.fm | Podcast hosting for EmpowerApps.Show: RSS feed, embedded player | PodcastItem+URLs.swift, TransistorPublishPlugin |
| YouTube Data API v3 | Video metadata merged into episode pages; thumbnails and embeds | import podcast, SwiftTube |
| Plausible | Privacy-focused web analytics | Node+Head.swift, Styling/scripts/index.ts |
| Buffer / Twitter / LinkedIn | Social sharing links | Nodes/Social/ |
| Google Fonts | Cardo, Oxygen, Oxygen Mono font loading | Styling/styles/styles.css |
| Docker Hub | swiftlang/swift:nightly-6.4.x-noble base image for Linux CI/builds |
Dockerfile |
| Codecov | Test coverage monitoring | codecov.yml |
| Secret Name | Service / Purpose | CI Job Where Used |
|---|---|---|
CONTENT_DEPLOY_KEY |
SSH deploy key (write access) enabling bot pushes to re-trigger CI deploys | automate-content |
NETLIFY_AUTH_TOKEN |
Authentication token for deploying static output to Netlify | deploy |
NETLIFY_PRODUCTION_SITE_ID |
Production site identifier for Netlify CLI deployment | deploy |
MAILCHIMP_API_KEY |
API key for fetching historical newsletter campaign archives | automate-content |
MAILCHIMP_LIST_ID |
Audience / List ID for Mailchimp newsletter campaign ingestion | automate-content |
BUTTONDOWN_API_KEY |
API key for importing newly published Buttondown emails | automate-content |
YOUTUBE_API_KEY |
API key for fetching video metadata via YouTube Data API v3 | automate-content |
Secrets are configured under Repository Settings BUTTONDOWN_API_KEY etc.).
- Swift 6.4 snapshot toolchain —
.swift-versionpins6.4.x-snapshot(swiftly install 6.4.x-snapshotor matching Xcode toolchain). Requires macOS 15+ for local builds (ConfigKeyKit 1.0.0-beta.2; Publish stack also usesSynchronization.Mutex). - mise — provides Node 20, swift-format, SwiftLint, and periphery at pinned versions (
.mise.toml), and setsNPM_PATHrequired by NPMPublishPlugin. - Docker / Devcontainer:
.devcontainer/andDockerfile(based onswiftlang/swift:nightly-6.4.x-noble) mirror the CI environment.
# Build and run tests
swift build
swift test
# Generate the site (drafts mode includes future-dated content)
swift run brightdigitwg publish --mode drafts
# Serve the static output locally using Ruby:
ruby -run -e httpd Output -p 8000
# Lint codebase (swift-format + SwiftLint + periphery)
./Scripts/lint.sh
# Scan content quality (report-only)
node Scripts/check-content.jsContent edits are plain Markdown under Content/; Swift changes to the theme live in Sources/BrightDigitSite/. CI builds every PR and pushes a non-production Netlify deploy preview. Development conventions are detailed in AGENTS.md.
The v2.0.0-alpha.2 checkpoint closed out the major modernization arc: Tailwind v4, the Buttondown migration, the Plot component migration, Swift 6.4 strict concurrency across the stack, and the de-vendoring release of all 20 packages.
Work is organized in .claude/PRD.md and 13 GitHub milestones:
| Phase | Focus | State |
|---|---|---|
| 1 — Package extraction | Move Sources/ into a standalone BrightDigitSite package repo; formalize repo boundaries |
Next up |
| 2 — AI-CITE content optimization | Per-article rewrites following the GEO audit | In progress |
| 3 — Site SEO code | Real dateModified sitewide, robots.txt, type-level SEO invariants |
Open |
| 5 — Publishing infrastructure | Wire content planning into the publish + fan-out pipeline | Partially done |
| 7 — Platform migration | Netlify → GitHub Pages, form-hosting decisions | Open |
| 8 — Final cleanup | Parallel page generation, periphery config, tech debt | Open |
Sources of truth: .claude/PRD.md for the roadmap and rationale, the milestone board for live counts, and .claude/MERGE-AND-TAG.md for the de-vendoring record.
For posterity, major architectural shifts and milestones are recorded below:
- #40: Replaced Ink's custom Markdown parser with
swift-markdown. - #42 & #48: Vendored initial subrepo dependency graph for strict concurrency modernization.
- #44: Replaced
swift-argument-parserwith declarativeConfigKeyKit+swift-configuration. - #144: Resolved rendered content quality defects and merge field encoding.
- #147 & #148: Buttondown migration & API client integration.
- #150: Tailwind CSS v4 migration in CSS-first mode.
- #151: Swift 6.4 strict concurrency enforcement across Plot, Publish, Ink, and Files.
- #157: Migrated Plot theme rendering from
NodeAPI toComponentAPI with zero HTML diffs. - #159 & #160: Multi-platform CI unification across Ubuntu, macOS, Windows, and Android.
- #161: Un-vendoring release of all 20 first-party Swift packages.