Skip to content

Commit 0727937

Browse files
authored
Merge pull request #6 from britoruben/sandbox
Single-source CV content, bilingual ES/EN site, and visual redesign
2 parents cc11451 + 53a3c3a commit 0727937

23 files changed

Lines changed: 2780 additions & 361 deletions

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.github/workflows/compile-latex.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
name: Compile LaTeX to PDF
22

3+
# main.tex is generated and committed, so filtering on it is enough: any
4+
# content change that affects the PDF shows up as a change to this file. A
5+
# content change committed WITHOUT regenerating is caught by verify-build,
6+
# which fails the push.
7+
#
8+
# NOTE: this job pushes with secrets.PDF_PUSH_TOKEN, a fine-grained PAT. The
9+
# repository ruleset requiring pull requests on main cannot be bypassed by
10+
# GITHUB_TOKEN (GitHub evaluates ruleset bypass against real actor identities,
11+
# not the ephemeral Actions token), which is why the PAT exists. PATs expire,
12+
# and when it does this job will start failing with a push permission error
13+
# rather than anything descriptive. A GitHub App installation token via
14+
# actions/create-github-app-token, added to the ruleset bypass list, would
15+
# remove the expiry entirely - it needs the App created and installed first.
16+
317
on:
418
push:
519
branches: [ main ]
@@ -44,7 +58,7 @@ jobs:
4458
- name: Generate PDF page-1 thumbnail
4559
run: |
4660
if [ -f "docs/RubenBrito-CV.pdf" ]; then
47-
pdftoppm -png -r 150 -singlefile docs/RubenBrito-CV.pdf docs/assets/cv-preview
61+
pdftoppm -png -r 60 -singlefile docs/RubenBrito-CV.pdf docs/assets/cv-preview
4862
fi
4963
5064
- name: Commit and push PDF

.github/workflows/pr-preview-pdf.yml

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
paths:
77
- 'main.tex'
88

9+
concurrency:
10+
group: pr-preview-pdf-${{ github.ref }}
11+
cancel-in-progress: true
12+
913
jobs:
1014
build-preview:
1115
runs-on: ubuntu-latest
@@ -34,11 +38,53 @@ jobs:
3438
uses: actions/github-script@v7
3539
with:
3640
script: |
41+
// Hidden marker used to find our own previous comment on this PR
42+
// so repeated pushes update one comment instead of piling up a
43+
// new one every time. Keep this exact string stable across edits
44+
// to this workflow -- changing it orphans any existing comment.
45+
const marker = '<!-- cv-pdf-preview -->';
46+
3747
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
38-
const body = `CV PDF preview built successfully. Download it from the workflow run artifacts:\n${runUrl}`;
39-
await github.rest.issues.createComment({
40-
issue_number: context.issue.number,
41-
owner: context.repo.owner,
42-
repo: context.repo.repo,
43-
body
48+
const headSha = context.payload.pull_request && context.payload.pull_request.head
49+
? context.payload.pull_request.head.sha.substring(0, 7)
50+
: context.sha.substring(0, 7);
51+
52+
const body = [
53+
marker,
54+
'### CV PDF Preview',
55+
'',
56+
`CV PDF preview built successfully from commit \`${headSha}\`. Download it from the workflow run artifacts (expires per repo artifact retention settings):`,
57+
runUrl,
58+
'',
59+
`<sub>Last updated by run #${context.runNumber}.</sub>`,
60+
].join('\n');
61+
62+
const { owner, repo } = context.repo;
63+
const issue_number = context.issue.number;
64+
65+
// List (with pagination) every comment on this PR and look for
66+
// one we posted previously, identified by the hidden marker.
67+
const comments = await github.paginate(github.rest.issues.listComments, {
68+
owner,
69+
repo,
70+
issue_number,
71+
per_page: 100,
4472
});
73+
74+
const existing = comments.find((c) => c.body && c.body.includes(marker));
75+
76+
if (existing) {
77+
await github.rest.issues.updateComment({
78+
owner,
79+
repo,
80+
comment_id: existing.id,
81+
body,
82+
});
83+
} else {
84+
await github.rest.issues.createComment({
85+
owner,
86+
repo,
87+
issue_number,
88+
body,
89+
});
90+
}

.github/workflows/verify-build.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Verify generated files
2+
3+
# The pages and main.tex are generated from content/cv.json. This job fails if
4+
# they were edited by hand, if someone changed the content without regenerating
5+
# them, or if a template change broke an internal link or anchor.
6+
7+
on:
8+
push:
9+
branches: [ main, sandbox ]
10+
pull_request:
11+
branches: [ main, sandbox ]
12+
13+
concurrency:
14+
group: verify-build-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
verify:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Node
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '20'
28+
29+
- name: Check that generated files match the content source
30+
run: npm run check
31+
32+
- name: Check internal links and anchors
33+
run: npm run check:links

README.md

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,84 @@ Personal homepage + LaTeX CV in one repo.
88

99
## Quick Start
1010

11-
1. Edit `main.tex` for the LaTeX CV content
12-
2. Edit `docs/index.html` / `docs/styles.css` for the website
13-
3. Push to `main` — a GitHub Actions workflow compiles `main.tex` and commits the PDF to `docs/RubenBrito-CV.pdf` automatically
14-
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
11+
All CV content lives in a single file, `content/cv.json`. The website and the
12+
LaTeX CV are generated from it — **never edit `docs/index.html` or `main.tex` by
13+
hand**, your changes will be overwritten on the next build.
14+
15+
1. Edit `content/cv.json`
16+
2. Run `npm run build` — regenerates `docs/index.html` and `main.tex`
17+
3. Commit both the source and the generated files
18+
4. Push to `sandbox`, then open a pull request to `main`
1519

1620
```bash
21+
npm run build
1722
git add -A && git commit -m "Update CV" && git push
1823
```
1924

25+
`npm run check` verifies the generated files are in sync with the content
26+
source without writing anything, and `npm run check:links` verifies every
27+
internal link and anchor resolves. CI runs both on every push and pull
28+
request.
29+
30+
Styling (`docs/styles.css`) and page structure (`tools/render-html.mjs`) are
31+
edited directly — only the *content* is generated.
32+
2033
## Structure
2134

22-
- **main.tex** - CV in LaTeX format (compiled to PDF by GitHub Actions)
23-
- **docs/index.html** - Personal website/portfolio, including a "Public Projects" section that fetches repos live from the GitHub API
24-
- **docs/styles.css** - Website styling (light/dark theme)
25-
- **docs/assets/** - Profile photo and favicon
26-
- **.github/workflows/compile-latex.yml** - Compiles the PDF and publishes it to `docs/` on every push to `main`
27-
- **.github/workflows/pr-preview-pdf.yml** - Compiles the PDF on pull requests to `main` and uploads it as a review artifact
35+
### Source (edit these)
36+
37+
- **content/cv.json** - single source of truth for all CV content
38+
- **tools/render-html.mjs** - website template
39+
- **tools/render-tex.mjs** - LaTeX body template
40+
- **tools/preamble.tex** - LaTeX preamble, macros and page setup
41+
- **tools/lib.mjs** - shared helpers (locale resolution, HTML/LaTeX escaping)
42+
- **tools/build.mjs** - build entry point (`npm run build` / `npm run check`)
43+
- **tools/icons.mjs** - inline SVG icons (no icon CDN)
44+
- **tools/check-links.mjs** - internal link and anchor checker (`npm run check:links`)
45+
- **docs/styles.css** - website styling (light/dark theme)
46+
- **docs/assets/** - personal mark and favicons
47+
48+
### Generated (do not edit)
49+
50+
- **docs/index.html** - website in English, built from `content/cv.json`
51+
- **docs/es/index.html** - Spanish version, same source
52+
- **docs/robots.txt**, **docs/sitemap.xml** - built from the site URL in `content/cv.json`
53+
- **main.tex** - LaTeX CV, built from `content/cv.json`
54+
- **docs/RubenBrito-CV.pdf** - compiled by GitHub Actions from `main.tex`
55+
- **docs/assets/cv-preview.png** - first page of the PDF, shown as a thumbnail on the site
56+
57+
### Workflows
58+
59+
- **.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
60+
- **.github/workflows/compile-latex.yml** - compiles the PDF and publishes it to `docs/` on every push to `main`
61+
- **.github/workflows/pr-preview-pdf.yml** - compiles the PDF on pull requests to `main` and uploads it as a review artifact
62+
63+
## Localisation
64+
65+
The site is bilingual. English is served at the site root, Spanish at `/es/`,
66+
with `hreflang` alternates and a per-locale canonical. Both come from the same
67+
`content/cv.json`: text fields are keyed by locale and fall back to English
68+
when a translation is missing, so a partial translation still builds.
69+
70+
The PDF is English only.
71+
72+
## Branches
73+
74+
Work happens on `sandbox` and reaches `main` through a pull request. `main` is
75+
what GitHub Pages serves.
76+
77+
## Maintenance note: the PDF push token
78+
79+
`compile-latex.yml` pushes the compiled PDF back to `main` using
80+
`secrets.PDF_PUSH_TOKEN`, a fine-grained PAT. It exists because the repository
81+
ruleset requiring pull requests on `main` cannot be bypassed by the default
82+
`GITHUB_TOKEN` - GitHub evaluates ruleset bypass against real actor identities,
83+
not the ephemeral Actions token.
84+
85+
**This PAT expires.** When it does, the workflow fails with a push permission
86+
error that does not mention expiry. Replacing it with a GitHub App installation
87+
token (`actions/create-github-app-token`) added to the ruleset bypass list
88+
would remove the expiry, at the cost of creating and installing an App.
2889

2990
## GitHub Pages Setup
3091

0 commit comments

Comments
 (0)