How to work on this repository: build the site, validate the specification, and understand what happens on deploy.
Prerequisites
| Tool | Why | Install |
|---|---|---|
| ssg | Generates the site | brew install spagu/tap/ssg |
| Node.js 20+ | Vendors the browser libraries; runs the schema validator | nodejs.org |
| Python 3.8+ | Runs the schema linting and comparison tools | Preinstalled on macOS and most Linux |
Only ssg and Node are needed to build the site. Python is needed for
make validate.
First build
git clone https://github.com/tradik/schema-resume.git
cd schema-resume
make install # npm ci — build dependencies only; nothing is published from here
make serve # http://127.0.0.1:8888, rebuilds on every save
make serve runs two steps:
npm run build:assetsvendors the browser libraries — Ajv, Chart.js, Mermaid, the XSLT processor — and inlines the icon set. Output lands intemplates/schema-resume/js/vendor/,editor/vendor/andtemplates/schema-resume/partials/icons.html. All generated, all gitignored.ssg --config .ssg.yamlgeneratesoutput/, publishing the specification,xml/,docs/and the editor straight from where they live viastatic_sources.
There is exactly one live copy of schema.json in this repository, at the root.
The snapshots under versions/ are frozen past releases and are never edited.
If you find yourself changing a file under output/, stop — the next build will
erase it.
Everyday commands
make help # every target, with a description
make site # build once into output/
make site-release # minified and fingerprinted, exactly as CI builds it
make check-links # fail on a dead internal link
make validate # lint and cross-check every schema file
make freeze-version # snapshot the current specification into versions/
make clean # remove generated output and vendored libraries
make site-release is worth running before opening a pull request: minification
and fingerprinting occasionally surface a template problem a development build
does not.
Validating the specification
npm test # compile schema.json and validate example.json with Ajv
make validate # the Python linting and comparison suite
The linter checks syntax and structure across all four specification files. The
comparison tool reports JSON-LD coverage — which schema fields have a @context
mapping, and therefore which produce RDF triples.
To validate your own document:
npx ajv validate -s schema.json -c ajv-formats -d your-resume.json
No --strict=false and no -r flag. $schema names the draft-07 dialect, so
any validator resolves it unaided, and the schema carries no keyword that ajv's
strict mode rejects.
Where things live
schema.json, meta-schema.json, the specification — the canonical copies
context.jsonld, example*.json
xml/1.0/ the XSD and its example
docs/ documentation; published as HTML and served raw
content/site/pages/ tool and legal pages (metadata only)
data/packages.yaml drives the packages page
templates/schema-resume/ the theme: templates, CSS, JS, images
tools/ build and validation scripts
workers/cookie-consent/ the Pages Function behind the consent banner
packages/ the six language validator packages
versions/ frozen past releases, served at /1.2/, /1.3/…
output/ generated — not in git
Adding a documentation page
Drop a Markdown file in docs/ with front matter:
---
title: "Short Title"
slug: "short-title"
status: "publish"
type: "page"
description: "One sentence, 70–160 characters — the build warns outside that range."
categories: [Documentation]
---
It appears at /short-title.html and in the guide list on the home page
automatically. The build checks that every page has a title and a description of
a sensible length, that no internal link is dead, that every image has alt
text, and that nothing is orphaned.
To keep a file in the repository without publishing it, add it to
content_exclude in .ssg.yaml.
Deployment
Pushing to main runs
.github/workflows/deploy-pages.yml,
which installs ssg, builds with --minify-all --fingerprint --check-links=strict, and deploys output/ to Cloudflare Pages. Pull requests
are built but not deployed, so a broken site is caught before merge.
Two secrets are required on the cloudflare-pagesenvironment (Settings →
Environments → cloudflare-pages → Environment secrets), not just the repository:
CLOUDFLARE_API_TOKEN (an API token with Cloudflare Pages: Edit) and
CLOUDFLARE_ACCOUNT_ID. Without them the Build job passes and the Publish job
fails with "it's necessary to set a CLOUDFLARE_API_TOKEN". The token needs
Cloudflare Pages: Edit on the account. The workflow creates the Pages project
schema-resume on first run if it is missing; attaching the custom domain
schema-resume.org to that project (Pages → schema-resume → Custom domains) is
a one-off manual step in the dashboard.
The consent banner's audit log needs a KV namespace bound to the Pages project
as CONSENT_LOG, plus a CONSENT_IP_SALT secret. Without them the banner still
works and still records the visitor's choice; only the proof-of-consent record
is skipped. See
workers/cookie-consent/README.md.
The checkout uses fetch-depth: 0 deliberately: sitemap <lastmod> values come
from each file's last commit, which a shallow clone does not have.
A local build cannot reproduce those dates. If ssg was installed as a snap
(the usual way on Linux), strict confinement means it cannot see the host's
git at all, so lastmod_from_git falls back to front-matter modified: dates
and, for files that have no front matter, to the file's modification time. ssg
1.8.58 and later warn once per build when this happens. Nothing needs fixing —
CI installs the release tarball, where git is on PATH — but it does mean the
<lastmod> values in a locally built output/sitemap.xml are not the ones that
get published. Use the Homebrew or tarball install if you need to check them.
Cloudflare settings this repository cannot set
Two dashboard toggles override what is committed here. Both default to a value that is wrong for this site, and neither leaves a trace in the build.
Email Address Obfuscation — turn it off. (Cloudflare dashboard → the zone →
Scrape Shield → Email Address Obfuscation.) It rewrites anything that looks like
an email address in the served HTML into a /cdn-cgi/l/email-protection link
whose visible text is the literal [email protected]. On a site whose subject is
a data format, that is not a privacy feature: it corrupts the example addresses
inside the JSON and JSON-LD samples, so a reader copying one out of the
documentation gets a document that does not validate. It also 404s, which is how
it was found — six pages linking to a broken /cdn-cgi/l/ URL in an Ahrefs
crawl. The theme wraps every page body in Cloudflare's <!--email_off-->
opt-out, but that is a second line of defence and the toggle is the fix.
AI Scrapers and Crawlers blocking — turn it off. (Security → Bots.) It
blocks at the edge the crawlers robots.txt explicitly invites, which is how the
site came to have an inconsistent AI bot policy: some AI bots allowed, others
blocked, with nothing in the repository asking for either. The specification is
MIT-licensed and every example address in it is fictional; being read is the
point of publishing it.
Releasing a schema version
Order matters: freeze before bumping, or the outgoing version's pinned URL picks up the incoming changes.
make freeze-version— snapshots the current specification intoversions/<MAJOR.MINOR>/. This is what/1.3/schema.jsonserves, and it is why a pin cannot change underneath a document.- Add the new snapshot to
static_sourcesin.ssg.yaml. - Update
versioninpackage.json,titleinschema.jsonandmeta-schema.json, andmeta.versionin the examples. - Update
schema_versionandschema_version_minorin.ssg.yaml, and the schema badge inREADME.md. - Add a
CHANGELOG.mdentry and, for a minor or major bump, a migration guide indocs/. See versioning and stability for which bump a change deserves. - Run
make validate && npm test && make site-release. - Run
./tools/sync-schema-files.shto copy the schema into every language package. - Tag
vMAJOR.MINOR.PATCHand push. That triggersrelease-packages.yml, which publishes to npm, PyPI, Go, Maven Central, RubyGems and Packagist.
Check the registries afterwards. The v1.2.0 release reported success while
npm actually failed with E404 — the log was piped through tee, which
swallowed the exit code. That specific masking is fixed, but a release is not
done until the versions are visible:
npm view schema-resume-validator version
curl -s https://pypi.org/pypi/schema-resume-validator/json | jq -r .info.version
curl -s https://rubygems.org/api/v1/gems/schema-resume-validator.json | jq -r .version