# Setup Guide

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

## Prerequisites

| Tool | Why | Install |
|---|---|---|
| [ssg](https://github.com/spagu/ssg) | Generates the site | `brew install spagu/tap/ssg` |
| Node.js 20+ | Vendors the browser libraries; runs the schema validator | [nodejs.org](https://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

```bash
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

```bash
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

```bash
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:

```bash
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:

```yaml
---
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`](../.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-pages` **environment** (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`](../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](VERSIONING.md) 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`](../.github/workflows/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:

```bash
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
```
