Build in GitLab CI
The Reed pipeline as .gitlab-ci.yml, with both caches, dist/ as an artifact, and a main-only deploy job.
This page is the GitLab version of Build in GitHub Actions:
check, prefetch on a cache miss, build, keep dist/ as an artifact, and deploy from the
default branch only.
It was written from GitLab’s CI/CD YAML reference and Reed’s cache layout, not from a pipeline that has run on GitLab. Read the checklist at the end before you trust the first green run.
Getting the Reed CLI
Section titled “Getting the Reed CLI”Reed is not published to npm yet. The job clones the Reed source, builds it, and runs
node .ci-reed/packages/cli/dist/bin.js. Two inputs control what it builds:
| Name | Where | Value |
|---|---|---|
REED_REPO_URL |
CI/CD variable, in project settings | Clone URL of the Reed source. Mark it masked if it carries a token. |
.reed-ref |
a file committed to your repository | One line: the Reed commit SHA to build. |
The pin lives in a file rather than a variable for a reason. GitLab’s cache:key:files
hashes file contents into the cache key, so changing the pin automatically starts a
fresh cache. The Reed revision is what decides what reed prefetch stores. Your content
does not.
The pipeline
Section titled “The pipeline”Save this as .gitlab-ci.yml:
stages: - build - deploy
variables: # Both caches that `reed prefetch` fills. GitLab only caches paths inside the project # directory, so both live under it. REED_COREPACK_HOME points Reed's Corepack calls at # the same directory as COREPACK_HOME. REED_STORE_DIR: "$CI_PROJECT_DIR/.ci-cache/reed-pnpm-store" COREPACK_HOME: "$CI_PROJECT_DIR/.ci-cache/corepack" REED_COREPACK_HOME: "$CI_PROJECT_DIR/.ci-cache/corepack"
build-docs: stage: build # Node 22, not "latest": prefetch and build shell out to Corepack, and Node 25 and # newer no longer bundle it. image: node:22-bookworm cache: key: files: - .reed-ref prefix: reed-node22-bookworm paths: - .ci-cache/reed-pnpm-store - .ci-cache/corepack script: - | set -eu if [ -z "${REED_REPO_URL:-}" ]; then echo "error: set the CI/CD variable REED_REPO_URL to the Reed source repository." >&2 exit 1 fi
# Build the Reed CLI from source. A throwaway COREPACK_HOME keeps this from # warming the cache the job is about to depend on. git clone --quiet "$REED_REPO_URL" .ci-reed git -C .ci-reed checkout --quiet "$(cat .reed-ref)" ( export COREPACK_HOME="$(mktemp -d)" cd .ci-reed corepack enable pnpm pnpm install --frozen-lockfile pnpm run build ) REED_BIN="$CI_PROJECT_DIR/.ci-reed/packages/cli/dist/bin.js" test -f "$REED_BIN"
# Offline, no prerequisites. node "$REED_BIN" check
# GitLab has no cache-hit flag. A missing cache directory is the miss. if [ -d "$REED_STORE_DIR" ] && [ -d "$COREPACK_HOME" ]; then echo "Reed caches restored; skipping prefetch." else node "$REED_BIN" prefetch fi
node "$REED_BIN" build --out dist artifacts: paths: - dist/ expire_in: 1 week
deploy-docs: stage: deploy image: node:22-bookworm needs: - job: build-docs artifacts: true rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual # REPLACE ME: switch to `when: on_success` once the script below uploads dist/ script: - | # REPLACE ME with your host's upload command for dist/. The job stays manual # until you do, so a merge never looks deployed when nothing was uploaded. echo "no deploy command configured yet; upload dist/ to your host here"What each part is doing
Section titled “What each part is doing”Both caches in one cache: block. reed prefetch fills the dependency store at
REED_STORE_DIR and Corepack’s cache of the pinned package-manager binary at
COREPACK_HOME. Cache only one and the build still passes on a runner with network
access. It fetches the other directory silently, every run.
Paths inside the project. GitLab resolves cache:paths and artifacts:paths
relative to $CI_PROJECT_DIR and does not reach outside it. Reed’s default store
location, ~/.reed/pnpm-store, would never be cached. That is why the variables move
both directories under .ci-cache/.
Prefetch only on a miss. When the key changes, nothing is restored, the directories
do not exist, and the job runs reed prefetch. GitLab saves the cache only when the job
succeeds, so a failed prefetch is never stored as a warm cache.
A stale cache fails loudly. If a restored store was prefetched for a different Reed
pin or platform, reed build stops with a diagnostic telling you to run reed prefetch.
It does not fall back to the network.
No pipes. Each Reed command runs on its own line. Piping one into tee reports the
exit status of tee, and a failed build passes. Redirect with > if you need a log file.
Artifacts, not a rebuild. deploy-docs uses needs with artifacts: true, so it
receives the exact dist/ that build-docs produced and checked.
Deploy from the default branch only
Section titled “Deploy from the default branch only”The rules: entry adds deploy-docs to a pipeline only when the commit is on the
project’s default branch. Every other branch still runs build-docs, so each push is
checked and built, and nothing but the default branch reaches your host.
What replaces the placeholder depends on your host. Reed has no deploy command. Keep the
host’s token in a masked CI/CD variable that only this job uses. If docs.json sets
base, stage dist/ under a matching directory before uploading, and keep _headers at
the root of the served tree. See Deploy and
Response headers.
Stricter checks
Section titled “Stricter checks”To fail on warnings too, change the check line:
node "$REED_BIN" check --strict-components --strict-tokens--strict-components escalates DS-D* design warnings. --strict-tokens escalates every
other warning. See Diagnostics.
Check these on the first run
Section titled “Check these on the first run”This pipeline has not been run on GitLab by the people who wrote it. Verify it once:
- Prefetch wrote inside the project. The first run logs
[reed] prefetch complete → store:followed by a path. It must end in.ci-cache/reed-pnpm-store. A path under~/.reedmeans the variables did not reach the job, and nothing will be cached. - Both paths were uploaded. The end of the job log shows the cache being created for
both
.ci-cachepaths. A warning that a path matched no files means that cache is empty. - The second run skips prefetch. It logs
Reed caches restored; skipping prefetch.and still builds. If it fails with a Reed diagnostic namingreed prefetch, the restored cache is stale or came from a different platform. - The artifact is the site. Browse the
build-docsjob artifacts and confirmdist/index.htmlanddist/llms.txtare there. - Deploy is gated. A pipeline on a feature branch shows no
deploy-docsjob.
Two cache behaviors will show up as extra prefetches. A cache is stored where the runner is installed unless distributed cache is enabled, so a job on a different runner sees a miss. And by default protected and unprotected branches do not share caches, so your default branch and your feature branches each warm their own. Both cost time, not correctness.
One key per platform
Section titled “One key per platform”Prefetch covers exactly the platform, architecture and libc it ran on. The cache prefix
names the image for that reason. If you add a job on an Alpine image, an ARM runner or a
macOS runner, give it a different prefix so the two never share a store.