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
143 changes: 143 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# pages -- publish README.md as the project website with Jekyll.
#
# The site at https://hexember.github.io/active-browser/ is README.md (served as
# index.html) plus the four root docs it links to: CONTRIBUTING.md, SECURITY.md,
# CODE_OF_CONDUCT.md and CHANGELOG.md, each rendered to <NAME>.html, plus the
# theme's own assets/css/... files. That set is an allowlist. _config.yml's
# exclude: keeps everything else out of the Jekyll source, and the "Published set
# is exactly the allowlist" step below fails the build, so nothing is uploaded,
# if anything else ends up in _site. Jekyll copies every non-Markdown file it is
# not told to exclude verbatim, which is how the old site came to serve a copy
# of install.sh.
#
# install.sh must never be served from Pages. The only install URL is
# raw.githubusercontent.com/hexember/active-browser/main/install.sh. A second
# copy on the site goes stale silently, and the old site's copy advertised the
# old, unowned install domain. The post-build check also fails if that domain
# string appears anywhere in the site.
#
# The zero-third-party-dependencies guardrail governs the Swift app. The
# actions/* steps here are GitHub's own first-party actions, the same category
# as actions/checkout in ci.yml. Adding a third-party action needs a re-plan
# first, and it must then be pinned to a full commit SHA, not a tag. There is no
# Gemfile and no Ruby setup: jekyll-build-pages brings its own pinned
# github-pages gem.
#
# Pages is configured as build_type: workflow, so this file is the only thing
# that updates the site. Deleting it freezes the site at its last deploy, which
# is how the old site went stale.
#
# Pull requests run the same build and allowlist check, so a leaked file or a
# missing page fails on the PR, before merge. The deploy job runs only for a
# push to main and for workflow_dispatch; on a pull_request it is skipped.
#
# workflow_dispatch from a branch other than main fails at the deploy job,
# because the github-pages environment only accepts main. This is expected.
name: pages

on:
push:
branches: [main]
paths:
- README.md
- CONTRIBUTING.md
- SECURITY.md
- CODE_OF_CONDUCT.md
- CHANGELOG.md
- _config.yml
- .github/workflows/pages.yml
pull_request:
paths:
- README.md
- CONTRIBUTING.md
- SECURITY.md
- CODE_OF_CONDUCT.md
- CHANGELOG.md
- _config.yml
- .github/workflows/pages.yml
workflow_dispatch:

permissions: {}

jobs:
build:
# jekyll-build-pages is a Docker container action; it does not run on macOS.
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pages: read
steps:
- uses: actions/checkout@v4

- uses: actions/configure-pages@v6

- uses: actions/jekyll-build-pages@v1
with:
source: ./
destination: ./_site

- name: Published set is exactly the allowlist
shell: bash
run: |
set -euo pipefail
fail=0

# 1. Nothing outside the allowlist. The theme's assets live under
# assets/{css,js,images,fonts}/; the repository's own assets/ is
# excluded and has no such subdirectories.
extra=$(cd _site && find . -type f | sed 's|^\./||' | sort \
| grep -vE '^(index|CONTRIBUTING|SECURITY|CODE_OF_CONDUCT|CHANGELOG)\.html$' \
| grep -vE '^README\.html$' \
| grep -vE '^assets/(css|js|images|fonts)/' || true)
if [ -n "$extra" ]; then
echo "::error::files outside the published allowlist (add to _config.yml exclude:, or to this allowlist and paths:)"
printf '%s\n' "$extra"
fail=1
fi

# 2. Every published page exists and is non-empty.
for f in index CONTRIBUTING SECURITY CODE_OF_CONDUCT CHANGELOG; do
if [ ! -s "_site/$f.html" ]; then
echo "::error::_site/$f.html is missing or empty"
fail=1
fi
done

# 3. The old, unowned install domain appears nowhere. Case-sensitive on
# purpose: ActiveBrowser.app is the bundle name.
if hits=$(grep -rl 'activebrowser\.app' _site); then
echo "::error::the old install domain appears in the site"
printf '%s\n' "$hits"
fail=1
fi

# 4. Warning only: a missing button must not block replacing a stale site.
if [ -f _site/index.html ] && ! grep -q 'View on GitHub' _site/index.html; then
echo "::warning::index.html has no 'View on GitHub' button"
fi

if [ "$fail" -ne 0 ]; then exit 1; fi
echo "published set matches the allowlist"

- uses: actions/upload-pages-artifact@v5

deploy:
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
# A running deployment is never cancelled mid-flight. Job-level, so PR
# builds never share this group and cannot cancel a queued main deploy.
concurrency:
group: pages
cancel-in-progress: false
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
9 changes: 8 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,16 @@ assets/ icon artwork (see assets/README.md)
install.sh the curl installer
Makefile build, bundle, install, release
docs/ background notes
.github/workflows/ CI and release
.github/workflows/ CI, release, and the GitHub Pages site
_config.yml GitHub Pages (Jekyll): which docs are published
```

`README.md` is also the website at <https://hexember.github.io/active-browser/>, rebuilt
on every push to `main`. A link in README (or in CONTRIBUTING, SECURITY, CODE_OF_CONDUCT
or CHANGELOG) must be absolute or point to one of those five `.md` files, because anything
else 404s on the site. A new file meant for the site must also be added to the allowlist
check and `paths:` in `.github/workflows/pages.yml`.

## Project history

Three directories are **history, not instructions**: `tasks/`, `Project.md`, and
Expand Down
4 changes: 4 additions & 0 deletions Project.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ One PR per task; a task never spans phases. Suggested split (the planner may spl
| 08 | 5 | *Browsers* include/exclude + last-item guard, *Fallback* radio | exclusion changes routing |
| 09 | 6 | `make release`, `install.sh` | one-liner installs from a local zip |
| 10 | 6 | `.github/workflows/release.yml` | tag builds and publishes assets |
| 18 | 6 | .github/workflows/pages.yml, _config.yml | pages workflow green; site shows README, no install.sh |


### Phase 1 — Core (`Core/`)
Expand Down Expand Up @@ -187,6 +188,7 @@ Goal: a user with no toolchain runs one command and has ActiveBrowser in `/Appli
Usage: `curl -fsSL https://raw.githubusercontent.com/hexember/active-browser/main/install.sh | sh`
- `make release`: `make bundle`, then `ditto -c -k --keepParent build/ActiveBrowser.app build/ActiveBrowser.app.zip` and `shasum -a 256` → `build/SHA256SUMS`.
- GitHub Actions `release.yml` on tag `v*`: `macos-latest` runner, `make release`, attach zip + `SHA256SUMS` to the Release with `gh release create`.
- Project site: `https://hexember.github.io/active-browser/` is `README.md` rendered by Jekyll (`jekyll-theme-cayman`) via `.github/workflows/pages.yml` on push to `main`. Only README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT and CHANGELOG are published (`_config.yml` `exclude:`, plus a post-build allowlist check in the workflow). `install.sh` is never served from Pages; the one install URL stays `raw.githubusercontent.com`.
- Signing: ad-hoc for v1. `curl` does not set the quarantine attribute, so an ad-hoc-signed bundle opens without Gatekeeper prompts via `install.sh`. Browser downloads and Homebrew *do* quarantine; if those paths are added later, add `make sign` (Developer ID) and `make notarize` (`notarytool`) targets first.
- Homebrew Cask: out of scope for v1.

Expand All @@ -212,7 +214,9 @@ active-browser/
├── Package.swift
├── Makefile
├── install.sh # Phase 6
├── _config.yml # Phase 6: GitHub Pages (Jekyll) config
├── .github/workflows/release.yml # Phase 6
├── .github/workflows/pages.yml # Phase 6: README → GitHub Pages
├── .claude/{agents,rules}/ # subagents + always-on rules
├── docs/skills.md
├── tasks/ # one file per task, from TEMPLATE.md (see CLAUDE.md)
Expand Down
49 changes: 49 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# GitHub Pages (Jekyll) config. The site is README.md plus the root docs it
# links to; see .github/workflows/pages.yml. Every non-Markdown file not listed
# under exclude is published verbatim.

title: "ActiveBrowser"
description: "A macOS menu bar agent that opens every link in the browser you were just using."
theme: jekyll-theme-cayman
repository: hexember/active-browser
show_downloads: false

readme_index:
enabled: true
remove_originals: true

optional_front_matter:
remove_originals: true

include: # these two are on jekyll-optional-front-matter's filename blacklist; include: whitelists them
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md

relative_links:
enabled: true
collections: false

titles_from_headings:
enabled: true
strip_title: true

# Names starting with ".", "_" or "#" are already skipped by Jekyll. Each entry
# is matched as a pattern or a path prefix, so never add a short prefix that
# could swallow a published file (e.g. "C" would exclude CONTRIBUTING.md).
exclude:
- tasks/
- docs/
- assets/
- Sources/
- Support/
- build/
- Project.md
- CLAUDE.md
- Makefile
- Package.swift
- Package.resolved
- install.sh
- LICENSE
- Gemfile
- Gemfile.lock
- vendor/
Loading
Loading