Lokasi ngalangkungan proxy:   [ UP ]  
[Ngawartoskeun bug]   [Panyetelan cookie]                
Skip to content

Adam-S-Daniel/adamdaniel.ai

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

334 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

adamdaniel.ai

Personal website and blog for Adam Daniel — Freelance AI Engineer.

Built with Jekyll + Decap CMS, deployed to S3 + CloudFront with an AWS Lambda OAuth proxy.

Architecture

adamdaniel.ai
├── Jekyll site          (S3 + CloudFront, custom domain)
├── Decap CMS            (/admin/ — headless CMS backed by this repo)
├── AWS OAuth Proxy      (Lambda + API Gateway HTTP API — ~$0/month)
├── Analytics            (CloudWatch RUM — see ANALYTICS_SETUP.md)
└── GitHub Actions       (production deploy + PR preview environments)

AWS Bootstrap (One-Time Setup)

Before GitHub Actions can deploy previews or infrastructure, bootstrap the AWS account with an OIDC identity provider, IAM role, and artifacts bucket.

Prerequisites: AWS CLI v2 with credentials configured, Route53 hosted zone for adamdaniel.ai.

# Deploy the bootstrap CloudFormation stack
bash infrastructure/bootstrap/deploy.sh

# If a GitHub OIDC provider already exists in this account:
CREATE_OIDC_PROVIDER=false bash infrastructure/bootstrap/deploy.sh

Then add the stack outputs as GitHub Actions secrets (repo → Settings → Secrets → Actions):

  • AWS_ROLE_ARN — the IAM role ARN for OIDC auth
  • PREVIEW_CLOUDFRONT_ID — the CloudFront distribution ID for preview cache invalidation

After verifying OIDC works, remove the old AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY secrets and deactivate the IAM user keys.

See infrastructure/bootstrap/template.yaml for full details on what is provisioned.

Content Model

Collection Type Key Fields
Posts Entry (folder: _posts/) title, body, date, tags, excerpt, featured_image, published, publish_date
Tags Entry (folder: _tags/) name, description
Projects Entry (folder: _projects/) title, description, images, url_link, technology, featured
Pages Entry (folder: pages/) title, body, permalink, published

reading_time is computed at build time from word count (÷200 wpm + 1) — there's no editor-facing field for it.

Atom feeds. The site exposes a global feed at /feed.xml (via jekyll-feed) plus per-tag feeds at /tags/<slug>/feed.xml (generated by _plugins/tag_feeds.rb). The two share a single template (_layouts/atom_feed.xml) so readers parse both identically. The RSS icon partial (_includes/feed-link.html) is mounted on the site-wide layout and on each tag page.

Editor's guide: see docs/CONTENT_GUIDE.md for a walkthrough of the CMS UI (sign-in, all four collections, image uploads, scheduling, previews, and the Save → PR → review → publish pipeline).

CMS Setup (/admin/)

Decap CMS is configured at /admin/config.yml. To activate:

  1. Create a GitHub OAuth App at https://github.com/settings/developers

    • Homepage URL: https://adamdaniel.ai
    • Callback URL: (set after deploying the OAuth proxy — see below)
  2. Deploy the OAuth proxy (see oauth-proxy/README.md)

    cd oauth-proxy
    export GITHUB_CLIENT_ID=your_id
    export GITHUB_CLIENT_SECRET=your_secret
    bash deploy.sh
  3. Update admin/config.yml with the deployed API URL:

    backend:
      name: github
      repo: Adam-S-Daniel/adamdaniel.ai
      branch: main
      base_url: https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com
      auth_endpoint: prod/auth
  4. Editorial workflow is on by default (publish_mode: editorial_workflow in admin/config.yml). Every Save in the CMS opens a PR on its own branch instead of committing straight to main, lighting up the cms/draftcms/ready labels, the per-PR preview-pr{N}.adamdaniel.ai environment, and the visual-regression review at /admin/reviews/.

Config gotcha: every folder collection in admin/config*.yml ships with explicit create: true AND delete: true. Decap defaults both to true, but spelling them out keeps editor capabilities visible in the YAML and survives any future major-version default change. files: collections (a fixed list of named entries) don't expose create or delete in the Decap UI — convert to a folder collection if editors need to add or remove entries.

Why Decap, not Sveltia: an earlier iteration of this repo used Sveltia CMS for its UX improvements, but Sveltia ≤ 0.158 silently ignores publish_mode: editorial_workflow (the upstream feature is on the 1.0 roadmap, not implemented yet). With branch protection on main, that meant every Save returned "Repository rule violations found / Changes must be made through a pull request." Decap implements the editorial workflow, so each Save creates a cms/... branch and opens a PR.

OAuth Proxy (AWS Lambda)

Located in oauth-proxy/. Implements the GitHub OAuth handshake required by Decap/Netlify CMS. Uses:

  • AWS Lambda (Python 3.12, 128 MB) — free tier: 1M requests/month
  • API Gateway HTTP API — cheapest API Gateway type, $1/M requests
  • No database, no VPC, no NAT — pure function

Estimated monthly cost: $0.00 for a personal blog (well within free tier).

Tests: cd oauth-proxy && python -m pytest test_lambda.py -v

Analytics (CloudWatch RUM)

Real-user monitoring (Core Web Vitals, JS errors, page-load timings) runs via Amazon CloudWatch RUM. Provisioned as a sibling CloudFormation stack adamdaniel-ai-rum so it lives or dies independently of the deploy pipeline:

bash infrastructure/rum/deploy.sh

Then paste the printed AppMonitorId and IdentityPoolId into _config.yml under analytics.cloudwatch_rum. The snippet in _includes/analytics/cloudwatch-rum.html is a no-op unless both JEKYLL_ENV=production AND a non-empty app_monitor_id are set, so local jekyll serve and PR previews stay silent.

Cost: roughly $0.10/month at this site's traffic. Full setup, sampling, retention, and exit-data instructions: ANALYTICS_SETUP.md.

GitHub Actions

Workflow Trigger What it does
deploy-production.yml Push to main Builds Jekyll, syncs to S3, invalidates CloudFront
deploy-preview.yml PR open/update Builds Jekyll, syncs to preview S3 prefix, posts URL comment
cms-editorial-workflow.yml PR from CMS Validates front matter, applies cms/draft label; auto-merges on cms/ready
visual-regression.yml PR open/update Screenshots every page on the PR vs prod, computes the per-page pixel diff (screenshots/regression/diffs.json), generates a 1920×1080 side-by-side video with a prominent VISUALLY DIFFERENT / IDENTICAL / NEW indicator per page, posts both stats to the PR, and gates merge on a human review at /admin/reviews/. The Waiting state on the approve-regression job is GitHub Environment gating — the job declares environment: regression-review and the environment has required reviewers configured in repo Settings → Environments. Time spent waiting for that approval does not count toward Actions minutes — no runner is allocated while the job is queued for review.
e2e-tests.yml PR + push to main Runs Playwright inside mcr.microsoft.com/playwright:v<version>-noble (browsers + apt deps prebaked, image tag drift-guarded against package-lock.json). On PRs, e2e/select-specs.js picks the spec subset and a dynamic shard count (1, 2, or 4) so small subsets don't pay for full fanout. The finalize job assembles per-test screenshot videos into the per-test-videos artifact. Concurrency cancels superseded PR runs.
secrets-scan.yml PR + push to main + weekly cron Runs gitleaks on the PR diff (or full history on push/cron). Allowlist for known test fixtures lives in .gitleaks.toml.
dependabot-auto-merge.yml PR opened/updated by dependabot[bot] Verifies the diff only touches dependency-manifest paths, then enables GitHub native auto-merge (--squash). Branch protection holds the merge until e2e + visual-regression checks pass. Configured by .github/dependabot.yml.
dependabot-comment-sync.yml PR opened/updated by dependabot[bot] touching workflows Pushes a follow-up commit refreshing every drifted # vX.Y.Z (YYYY-MM-DD) action-pin comment to match the new SHA's actual tag and tag-commit date. Requires the ADAMDANIELAI_WORKFLOW_SHA_COMMENT_PAT repo secret (fine-grained PAT with workflows: write); self-skips with a notice when unset.

Path-based skip rules (don't burn CI on docs/CI-only changes)

Most workflows declare path filters on their triggers so diffs which can't possibly affect a given workflow's output never allocate a runner. See AGENTS.md § Salient paths per workflow for the full per-workflow table; key entries:

Workflow Filter Effect
deploy-production.yml paths-ignore (push to main) Skips the prod deploy when a push only touches docs, test/CI tooling, non-Jekyll infra, or unrelated workflow files. The workflow's own file is salient. workflow_dispatch ignores paths-ignore.
visual-regression.yml paths (positive list) Only runs when the diff can actually shift rendered output or changes one of the tools the regression pipeline uses.
e2e-tests.yml paths-ignore (PR + push to main) Coarse-skip for docs-only / unrelated-CI-only diffs. e2e/select-specs.js still does the fine-grained "which specs to run" cut at runtime for diffs that do affect anything testable.
cms-publish-loop-host.yml, cms-publish-loop-prod.yml paths (positive, PR only) Workflow-level path filter — these specs are not in the required-status-checks list, so workflow-level filtering is safe (no missing-check trap). The schedule/dispatch triggers always fire regardless of paths.
regenerate-manual.yml, skills-mirror.yml, dependabot-comment-sync.yml paths (positive) Each filtered to its actual surface — manual-capturing specs, skills mirror tree, workflow files + the comment-sync script respectively.

deploy-preview.yml is intentionally not path-filtered — its closed-action teardown step needs to run on every PR close, and paths-ignore would also skip teardown, leaking preview deployments. The build cost is moderate (~2-3 min) and the safety win of always tearing down the per-PR S3 prefix outweighs the savings.

secrets-scan.yml, cms-editorial-workflow.yml, dependabot-auto-merge.yml, and publish-scheduled-posts.yml are all either security-critical, label-driven (cheap), or cron-driven, so they have no path filter.

Why the Deployments page is mostly red ❌ (by design)

The regression-review entries on GitHub's Deployments page show up as red, but none of them are deploy failures. regression-review is not a deploy target — it's a human-approval gate. When visual-regression.yml detects visually-different pages, its approve-regression job declares environment: regression-review to pause for a reviewer (configured in repo Settings → Environments). GitHub models that pause as a "deployment" stuck in waiting. The next push to the same PR supersedes the pending approval and GitHub flips its status from waitingerror, which the Deployments page renders as red. Every iteration on a PR with visual diffs therefore leaves a trail of red entries — all expected.

The github-pages entries are dormant history from before the 2026-03-15 cutover to S3 + CloudFront in deploy-production.yml. One genuinely errored on first attempt; the rest succeeded and were auto-deactivated when superseded.

Real deploy outcomes live in the Actions tab, not the Deployments page.

Preview Environments

PR previews are deployed to S3 and served via CloudFront with HTTPS:

  • URL pattern: https://preview-pr{N}.adamdaniel.ai/ (paths identical to prod)
  • Bucket: adamdaniel-ai-previews (one prefix per PR: /pr-{N}/)
  • CDN: single CloudFront distribution with wildcard ACM cert
  • Host-to-prefix mapping: a viewer-request CloudFront Function prepends /pr-{N} on every request; a viewer-response Function strips the same prefix from Location headers so S3's trailing-slash redirects (/admin/admin/) don't leak the internal key space
  • Teardown: auto-deleted when the PR is closed/merged

Required secrets:

  • AWS_ROLE_ARN (see AWS Bootstrap)
  • PREVIEW_CLOUDFRONT_ID — CloudFront distribution ID (output from bootstrap stack)

Local Development

# One-shot setup: installs everything needed to run the full test stack
# locally on Debian/Ubuntu/WSL2 — apt packages (libnspr4, libnss3, ffmpeg,
# ruby-full, python3), Bundler + Gemfile gems, npm deps, Playwright
# browser binaries, and a project-local pytest venv. Idempotent.
bash scripts/setup-test-environment.sh

# Build and serve
bundle exec jekyll serve --livereload

# Site:        http://localhost:4000
# CMS admin:   http://localhost:4000/admin/index-local.html  (uses config-local.yml)

# Run e2e tests (full matrix)
npx playwright test

# Run only the specs that the current diff can affect
node e2e/select-specs.js | jq -r '.files[]?' | xargs npx playwright test

# Decap admin smoke test (boots decap-server + a static fileserver)
npx playwright test e2e/cms-smoke.spec.js --project chromium-desktop

# Other suites
bundle exec ruby _plugins_test/auto_tag_pages_test.rb   # Jekyll plugin unit tests
cd oauth-proxy && python3 -m pytest test_lambda.py -v   # OAuth proxy Lambda tests

For the full test strategy — categories, trigger map, per-spec walkthrough, known gaps, and the recipe for adding tests when a new content collection ships — see docs/TESTING.md.

Contributor Manual

Editor-facing screenshots and step-by-step walkthroughs live in docs/CONTRIBUTOR_MANUAL.md. The manual is auto-generated by the e2e tests — every screenshot and caption is captured during a real Playwright run. If a step looks wrong, fix the captureStep(...) call in the spec under each screenshot, push, and the manual regenerates on the next run of .github/workflows/regenerate-manual.yml.

Status vs. Published

When editing posts via the CMS, two independent dimensions decide whether content reaches the live site. They look similar in the UI but gate two different things:

Dimension What it controls Where it lives
Status (Draft / In Review / Ready) Whether the PR gets merged into its base branch Decap's editorial workflow — translates to cms/draft / cms/ready PR labels in cms-editorial-workflow.yml. Auto-merge fires on cms/ready.
Published toggle Whether the post, once on its base branch, is rendered on the live site Custom front-matter field. Jekyll filters published: false out of site.posts at build time.

Status = "is this change ready?" Published = "should this post be visible right now?"

Editor-facing detail: see docs/CONTENT_GUIDE.md § Status vs. Published for the full matrix, including what changes when editing on a preview branch (preview-pr<N>.adamdaniel.ai) instead of production (adamdaniel.ai).

Branches

Branch / pattern Created by Merges into What it represents
main the only long-lived branch — (production) Single source of truth. Every push triggers deploy-production.yml.
feature/* (by convention) a developer main Code changes (workflows, CSS, plugins, infra). PR previews live at preview-pr<N>.adamdaniel.ai. The current example: restore-decap-cms (PR #48).
cms/<collection>/<slug> Decap, when editing on adamdaniel.ai/admin/ main A single CMS edit in editorial workflow. Auto-merges to main once cms/ready is set and checks pass. Production-bound.
cms/<collection>/<slug> (off a feature branch) Decap, when editing on preview-pr<N>.adamdaniel.ai/admin/ the feature branch (e.g. restore-decap-cms) A CMS edit made on a feature-branch preview to demonstrate / test changes. Stays on the preview tree — content edits here are not meant to reach production. Decap's branch name doesn't encode the parent, so the admin shows the active backend branch in its commit pill (top-right) when it isn't main. PRs from cms/... into a non-main base get the cms/preview-only label automatically.
cms/draft-<timestamp> Decap legacy fallback (older versions / no slug yet) same rules as above Same role as cms/<collection>/<slug> — the timestamped form is what Decap used in earlier releases.

Standing rule: content additions made on a feature branch's preview admin (the cms/... PRs targeting the feature branch) should be dropped when the feature branch itself merges into main. They exist to exercise the preview environment, not to ship.

Repository layout

Folders grouped by purpose. Anything not listed is incidental.

Content (what lives on the live site)

Path Holds
_posts/ Blog posts. Filename YYYY-MM-DD-<slug>.md is required by Jekyll; live URL is /blog/<slug>/ (no date prefix, see _config.yml's permalink).
_tags/ Curated tag descriptions. Optional — auto_tag_pages.rb synthesises archive pages for any tag a post uses.
_projects/ Project case studies.
pages/ Standalone pages with their own permalink (e.g. /about/). Currently disabled on the public route.
assets/images/uploads/ Editor-uploaded images, bucketed by YYYY/MM/. The CMS's media_folder config points here.

Appearance (how it looks)

Path Holds
_layouts/ Page templates (default.html, post.html, page.html, project.html, preview.html). Changes here re-render every page that uses the layout.
_includes/ Partial templates (header, footer, head, etc.) reused across layouts.
assets/css/ Stylesheets compiled into the site CSS.
assets/images/ (non-uploads/) Static design assets (logo, OG images, decorative SVG).
assets/js/ Static JS shipped with the site (e.g. marked.min.js).
admin/custom.css Cobalt-thermal theme for the Decap CMS shell.

Editorial / CMS surface

Path Holds
admin/ Decap CMS shell — index.html (production), index-local.html (local dev), config.yml / config-local.yml, preview-bridge.js, custom.css, the reviews/ dashboard.
preview.md + _layouts/preview.html The live-preview shell at /preview/ that the admin's preview bridge feeds.

Site framework

Path Holds
_config.yml, Gemfile Jekyll configuration and Ruby dependencies.
_plugins/ Site-build Ruby plugins (auto_tag_pages.rb, normalize_empty_slug.rb).
_plugins_test/ Plain-Ruby unit tests for _plugins/.
_data/ Build-time data (e.g. reading_times.yml).

Tests

Path Holds
e2e/ Playwright specs + helpers. See docs/TESTING.md for the per-spec walkthrough.
playwright.config.js, playwright.regression.config.js Test runner configuration.
oauth-proxy/test_lambda.py OAuth Lambda unit tests.

Infrastructure

Path Holds
infrastructure/bootstrap/ One-time AWS setup (CloudFormation templates, deploy scripts).
oauth-proxy/ AWS Lambda + API Gateway implementing the GitHub OAuth handshake Decap requires.
.github/workflows/ CI/CD: deploy-production, deploy-preview, e2e-tests, visual-regression, cms-editorial-workflow, publish-scheduled-posts.
scripts/ Build-time helpers (patch-preview-config.sh, setup-test-environment.sh, write-commit-json.sh, generate-showcase.js, etc.).
docs/ Long-form documentation: CONTENT_GUIDE.md, TESTING.md.

Branching Strategy (visual)

main                                  ← production (deploys automatically)
 ├─ feature/*                         ← code changes (e.g. restore-decap-cms)
 │   └─ cms/<collection>/<slug>       ← CMS edits made on the feature's preview admin
 │                                     (preview-only — don't carry to main)
 └─ cms/<collection>/<slug>           ← CMS edits made on adamdaniel.ai/admin/
     └─ PR opened, cms/draft applied  ← preview URL deployed, regression video posted
         └─ cms/ready                 ← auto-merged to main, preview cleaned up

About

The adamdaniel.ai website

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors