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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
16 changes: 15 additions & 1 deletion .github/workflows/compile-latex.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
name: Compile LaTeX to PDF

# main.tex is generated and committed, so filtering on it is enough: any
# content change that affects the PDF shows up as a change to this file. A
# content change committed WITHOUT regenerating is caught by verify-build,
# which fails the push.
#
# NOTE: this job pushes with secrets.PDF_PUSH_TOKEN, a fine-grained PAT. The
# repository ruleset requiring pull requests on main cannot be bypassed by
# GITHUB_TOKEN (GitHub evaluates ruleset bypass against real actor identities,
# not the ephemeral Actions token), which is why the PAT exists. PATs expire,
# and when it does this job will start failing with a push permission error
# rather than anything descriptive. A GitHub App installation token via
# actions/create-github-app-token, added to the ruleset bypass list, would
# remove the expiry entirely - it needs the App created and installed first.

on:
push:
branches: [ main ]
Expand Down Expand Up @@ -44,7 +58,7 @@ jobs:
- name: Generate PDF page-1 thumbnail
run: |
if [ -f "docs/RubenBrito-CV.pdf" ]; then
pdftoppm -png -r 150 -singlefile docs/RubenBrito-CV.pdf docs/assets/cv-preview
pdftoppm -png -r 60 -singlefile docs/RubenBrito-CV.pdf docs/assets/cv-preview
fi

- name: Commit and push PDF
Expand Down
58 changes: 52 additions & 6 deletions .github/workflows/pr-preview-pdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
paths:
- 'main.tex'

concurrency:
group: pr-preview-pdf-${{ github.ref }}
cancel-in-progress: true

jobs:
build-preview:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -34,11 +38,53 @@ jobs:
uses: actions/github-script@v7
with:
script: |
// Hidden marker used to find our own previous comment on this PR
// so repeated pushes update one comment instead of piling up a
// new one every time. Keep this exact string stable across edits
// to this workflow -- changing it orphans any existing comment.
const marker = '<!-- cv-pdf-preview -->';

const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = `CV PDF preview built successfully. Download it from the workflow run artifacts:\n${runUrl}`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
const headSha = context.payload.pull_request && context.payload.pull_request.head
? context.payload.pull_request.head.sha.substring(0, 7)
: context.sha.substring(0, 7);

const body = [
marker,
'### CV PDF Preview',
'',
`CV PDF preview built successfully from commit \`${headSha}\`. Download it from the workflow run artifacts (expires per repo artifact retention settings):`,
runUrl,
'',
`<sub>Last updated by run #${context.runNumber}.</sub>`,
].join('\n');

const { owner, repo } = context.repo;
const issue_number = context.issue.number;

// List (with pagination) every comment on this PR and look for
// one we posted previously, identified by the hidden marker.
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});

const existing = comments.find((c) => c.body && c.body.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}
33 changes: 33 additions & 0 deletions .github/workflows/verify-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Verify generated files

# The pages and main.tex are generated from content/cv.json. This job fails if
# they were edited by hand, if someone changed the content without regenerating
# them, or if a template change broke an internal link or anchor.

on:
push:
branches: [ main, sandbox ]
pull_request:
branches: [ main, sandbox ]

concurrency:
group: verify-build-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Check that generated files match the content source
run: npm run check

- name: Check internal links and anchors
run: npm run check:links
81 changes: 71 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,84 @@ Personal homepage + LaTeX CV in one repo.

## Quick Start

1. Edit `main.tex` for the LaTeX CV content
2. Edit `docs/index.html` / `docs/styles.css` for the website
3. Push to `main` — a GitHub Actions workflow compiles `main.tex` and commits the PDF to `docs/RubenBrito-CV.pdf` automatically
4. Opening a pull request against `main` that touches `main.tex` triggers a second workflow that compiles the PDF and uploads it as a downloadable build artifact, so it can be reviewed before merging
All CV content lives in a single file, `content/cv.json`. The website and the
LaTeX CV are generated from it — **never edit `docs/index.html` or `main.tex` by
hand**, your changes will be overwritten on the next build.

1. Edit `content/cv.json`
2. Run `npm run build` — regenerates `docs/index.html` and `main.tex`
3. Commit both the source and the generated files
4. Push to `sandbox`, then open a pull request to `main`

```bash
npm run build
git add -A && git commit -m "Update CV" && git push
```

`npm run check` verifies the generated files are in sync with the content
source without writing anything, and `npm run check:links` verifies every
internal link and anchor resolves. CI runs both on every push and pull
request.

Styling (`docs/styles.css`) and page structure (`tools/render-html.mjs`) are
edited directly — only the *content* is generated.

## Structure

- **main.tex** - CV in LaTeX format (compiled to PDF by GitHub Actions)
- **docs/index.html** - Personal website/portfolio, including a "Public Projects" section that fetches repos live from the GitHub API
- **docs/styles.css** - Website styling (light/dark theme)
- **docs/assets/** - Profile photo and favicon
- **.github/workflows/compile-latex.yml** - Compiles the PDF and publishes it to `docs/` on every push to `main`
- **.github/workflows/pr-preview-pdf.yml** - Compiles the PDF on pull requests to `main` and uploads it as a review artifact
### Source (edit these)

- **content/cv.json** - single source of truth for all CV content
- **tools/render-html.mjs** - website template
- **tools/render-tex.mjs** - LaTeX body template
- **tools/preamble.tex** - LaTeX preamble, macros and page setup
- **tools/lib.mjs** - shared helpers (locale resolution, HTML/LaTeX escaping)
- **tools/build.mjs** - build entry point (`npm run build` / `npm run check`)
- **tools/icons.mjs** - inline SVG icons (no icon CDN)
- **tools/check-links.mjs** - internal link and anchor checker (`npm run check:links`)
- **docs/styles.css** - website styling (light/dark theme)
- **docs/assets/** - personal mark and favicons

### Generated (do not edit)

- **docs/index.html** - website in English, built from `content/cv.json`
- **docs/es/index.html** - Spanish version, same source
- **docs/robots.txt**, **docs/sitemap.xml** - built from the site URL in `content/cv.json`
- **main.tex** - LaTeX CV, built from `content/cv.json`
- **docs/RubenBrito-CV.pdf** - compiled by GitHub Actions from `main.tex`
- **docs/assets/cv-preview.png** - first page of the PDF, shown as a thumbnail on the site

### Workflows

- **.github/workflows/verify-build.yml** - fails if the generated files are out of sync with `content/cv.json`, or if an internal link or anchor is broken
- **.github/workflows/compile-latex.yml** - compiles the PDF and publishes it to `docs/` on every push to `main`
- **.github/workflows/pr-preview-pdf.yml** - compiles the PDF on pull requests to `main` and uploads it as a review artifact

## Localisation

The site is bilingual. English is served at the site root, Spanish at `/es/`,
with `hreflang` alternates and a per-locale canonical. Both come from the same
`content/cv.json`: text fields are keyed by locale and fall back to English
when a translation is missing, so a partial translation still builds.

The PDF is English only.

## Branches

Work happens on `sandbox` and reaches `main` through a pull request. `main` is
what GitHub Pages serves.

## Maintenance note: the PDF push token

`compile-latex.yml` pushes the compiled PDF back to `main` using
`secrets.PDF_PUSH_TOKEN`, a fine-grained PAT. It exists because the repository
ruleset requiring pull requests on `main` cannot be bypassed by the default
`GITHUB_TOKEN` - GitHub evaluates ruleset bypass against real actor identities,
not the ephemeral Actions token.

**This PAT expires.** When it does, the workflow fails with a push permission
error that does not mention expiry. Replacing it with a GitHub App installation
token (`actions/create-github-app-token`) added to the ruleset bypass list
would remove the expiry, at the cost of creating and installing an App.

## GitHub Pages Setup

Expand Down
Loading
Loading