# Consumer conventions These shared actions and workflows are deliberately thin — they assume the consuming repo follows the FSH conventions below. If a repo diverges, override the relevant input or keep a local definition for that one piece. ## Repo-wide - **Org / default branch.** All repos live under `FSHTech` and use `main` as the default branch (release, docs, and sibling clones all target `main`). - **Pin to `@v1`.** Consumers reference everything at the moving major tag (`…@v1`), updated in this repo via `git tag -f v1 && git push -f origin v1`. - **Thin callers.** Each repo keeps its own `.github/workflows/*.yml`, but those files only own the `on:` trigger + inputs and delegate to the reusable workflows / composite actions here. Job orchestration that is genuinely repo-specific (the `lint` job graph) stays local but is *built from* these actions. - **`actions/checkout` first.** Every composite action assumes the repo is already checked out and (for lint/test/build actions) the language toolchain action has already run. ## Everything runs through `just` Lint, type-check, test, build and generate all run via `just-run` (`just `). The language-specific part is only the install that runs first (`setup-python` + `uv-sync`, or `setup-node`). So **every repo exposes its CI steps as `just` recipes in a root `justfile`** — the canonical names are `lint`, `type-check`, `coverage-ci` (CI test gate), `docs`, and for app/template repos `gen` / `bootstrap` / `validate`. This is what lets one generic runner serve both the Python and the Node repos. ## Python repos (`setup-python` → `uv-sync` → `just-run`, plus `pre-commit`) - **uv project** with a committed `uv.lock` at the repo root (the `setup-python` cache keys on it). Python is pinned to 3.14 unless `python-version` is overridden. - **Dependency groups.** `dev` (tests + general dev) and `lint` (ruff, zuban, pre-commit, and anything the local hooks import). Jobs pass these to `uv-sync` via `args`: `--group dev --group lint` (lint), `--group lint` (pre-commit's default sync), `--group dev` (tests). Monorepos add `--all-packages`. - **Justfile** with at least `lint`, `type-check`, `coverage-ci` recipes (run by `just-run`), and `docs` if the repo publishes Sphinx docs. - **`.pre-commit-config.yaml`** at the repo root. `ruff` / `ruff-format` are skipped by `pre-commit` (the lint job covers them); list any other hooks that can't run in CI via the `skip` input (e.g. `js-format`, `check-control-blank-lines` when that tree isn't materialised). - **Coverage** configured in `pyproject.toml`; the `coverage-ci` recipe runs `coverage run -m pytest` + a report. DB-backed suites attach a `postgres` service in the calling job and pass `DATABASE_URL` via env. ## Node repos (`setup-node` → `just-run`) - **`.nvmrc`** at the repo root pins the Node version. - **Yarn Classic (v1).** `setup-node` runs `yarn install --frozen-lockfile`; use `cache: yarn` layout and a committed `yarn.lock`. - **Yarn workspaces** with `@fsh/*` scoped package names. - **Justfile** exposing the same recipe names (`lint`, `type-check`, `build`, `test`) that wrap the underlying `yarn workspace …` calls, so `just-run` drives Node the same way it drives Python. (A repo that still calls `yarn` directly in its workflow hasn't adopted the runner yet — give it a `justfile`.) - **`LINT_IMPORT_CYCLES`** is honored by the shared eslint flat-config preset (turns on `import/no-cycle`); set it in the `lint` recipe or the job env. ## Sibling path-deps (`clone-siblings`) **Why clone instead of installing from CodeArtifact?** These deps *are* published CodeArtifact packages — `[tool.uv.sources]` (and the JS `file:` links) just override them to point at sibling *source*. The integration harnesses (`codegen-example-app`, `sales-pipeline`, `codegen-targets`, the templates) clone siblings so CI validates against **HEAD of the whole constellation**, not the last release — a breaking change in `codegen` is caught by the example app's generate/lint/test gates *before* it's published. Pure consumers of stable libs don't need this and should just resolve from CodeArtifact. Repos that do use the sibling layout follow the "checked out next to each other" convention: - `[tool.uv.sources]` paths are `../../` and JS `file:` links are `../..//packages/` — i.e. siblings sit one directory **above** `$GITHUB_WORKSPACE`. `clone-siblings` clones into `$(dirname $GITHUB_WORKSPACE)` to match. - **Per-repo deploy keys.** Each sibling has its own read-only deploy key (provisioned by the infra source-code stack). The consumer must be granted those keys (the stack's `ci_consumers` entry) and expose them as Actions secrets named `DEPLOY_KEY_` — repo name uppercased with hyphens turned into underscores (`codegen-database` → `DEPLOY_KEY_CODEGEN_DATABASE`). The caller passes them through on the step's `env:`. ## PR titles (`pr-title.yml`) - PR titles follow Conventional Commits. Each repo passes its allowed `scopes` (always include `deps`, `ci`, `release`). `requireScope` defaults to false. ## Releases (`release-python.yml`, `release-npm.yml`) - **release-please** configured in-repo: `release-please-config.json` + `.release-please-manifest.json`. Single-package repos emit `release_created`; monorepos emit `releases_created` (+ `paths_released` for npm). - **Optional `RELEASE_TOKEN`** secret (a PAT so release-please PRs re-trigger CI); falls back to `GITHUB_TOKEN`. Pass via `secrets: inherit`. - **CodeArtifact via OIDC** — infra provides these as **repo/org variables**: `CODEARTIFACT_PUBLISH_ROLE_ARN`, `CODEARTIFACT_REGION`, `CODEARTIFACT_DOMAIN`, `CODEARTIFACT_DOMAIN_OWNER`, and the repo URL (`CODEARTIFACT_PYPI_REPOSITORY_URL` for python, `CODEARTIFACT_NPM_REPOSITORY_URL` for npm). The publish job requests `id-token: write` and assumes the publisher role — no static credentials. - **Build system.** Python repos build with `uv build` (pyproject `build-system`); npm repos build only the buildable `@fsh/*` workspaces (the `build-command` input — `@fsh/eslint-config` has no build script and publishes source as-is). ## Docs (`docs.yml`) - A **Cloudflare Pages project is pre-provisioned** by infra, which supplies the `CLOUDFLARE_API_TOKEN` secret (pass via `secrets: inherit`) and the `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_PAGES_PROJECT` variables, with the custom domain bound to the `main` (production) branch. - **Python docs**: `just docs` builds Sphinx into `docs/_build/html`. **Node docs**: a Storybook build assembled into a site dir. The caller passes the `runtime`, `build-command`, and `output-dir`. - A docs build needing a **service container** (codegen-database's Sphinx build talks to postgres) can't use `docs.yml` — `services:` can't pass through `workflow_call` — so it keeps a local docs workflow. ## Tooling versions - **OPA / Regal** are pinned (`setup-opa` defaults 1.16.2 / 0.34.1) — bump deliberately, since OPA's `--schema` typechecker and Regal's lint rules evolve and can surface new failures.