How to work on this repository: build the site, validate the specification, and understand what happens on deploy.

Prerequisites

ToolWhyInstall
ssgGenerates the sitebrew install spagu/tap/ssg
Node.js 20+Vendors the browser libraries; runs the schema validatornodejs.org
Python 3.8+Runs the schema linting and comparison toolsPreinstalled 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:

  1. npm run build:assets vendors the browser libraries — Ajv, Chart.js, Mermaid, the XSLT processor — and inlines the icon set. Output lands in templates/schema-resume/js/vendor/, editor/vendor/ and templates/schema-resume/partials/icons.html. All generated, all gitignored.
  2. ssg --config .ssg.yaml generates output/, publishing the specification, xml/, docs/ and the editor straight from where they live via static_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.

  1. make freeze-version — snapshots the current specification into versions/<MAJOR.MINOR>/. This is what /1.3/schema.json serves, and it is why a pin cannot change underneath a document.
  2. Add the new snapshot to static_sources in .ssg.yaml.
  3. Update version in package.json, title in schema.json and meta-schema.json, and meta.version in the examples.
  4. Update schema_version and schema_version_minor in .ssg.yaml, and the schema badge in README.md.
  5. Add a CHANGELOG.md entry and, for a minor or major bump, a migration guide in docs/. See versioning and stability for which bump a change deserves.
  6. Run make validate && npm test && make site-release.
  7. Run ./tools/sync-schema-files.sh to copy the schema into every language package.
  8. Tag vMAJOR.MINOR.PATCH and push. That triggers release-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

Edit this page on GitHub