Using dash0hq/sync-docs-action
dash0hq/sync-docs-action is a composite GitHub Action that transforms documentation in a source
repository (e.g. dash0hq/otel-cicd-action) according to that repo's transformations.yaml, then
opens or updates a pull request in a target documentation repository (Dash0's website repo). The
caller checks out its own repo, then invokes this action; the transform and PR flow live in the action
so every source repo shares one implementation.
This skill is a set of workflows. Identify the task, then follow the matching workflow top to bottom.
- Wiring up a new sync in a source repo → Workflow A.
- Authoring or editing
transformations.yaml → Workflow B.
- Verifying changes before pushing → Workflow C.
- Migrating a caller to a newer action version → Workflow D.
- Diagnosing a failing run → Workflow E.
Reference: worked examples in production
Three Dash0 repos consume this action. Read them before wiring or editing a caller — they are the
canonical, working shapes to copy from.
dash0hq/dash0-operator — a flat sync (all pages land as siblings under one directory) with a
coverage: guard, pinned past the breaking release so it passes the target inputs from secrets.
dash0hq/dash0-cli — a nested-nav sync that groups pages sitting in a github-actions/
subdirectory via nav.groupTitles. Still pinned to v0.3.0, so it is also a live example of a caller
that has not yet done the D1 migration (its target coordinates still rely on the old defaults).
dash0hq/dash0-sdk-web — a flat nav: sync (a nav: block, but every page lands directly
under one directory, so no groupTitles), pinned past the breaking release with pr-reviewers set.
Still on a feature branch, so link to the add-sync-docs-to-website branch, not main.
Re-point these two links to main once the add-sync-docs-to-website PR merges; the branch links
break when that branch is deleted.
These repos deliberately differ in details you must not copy blindly. The operator and sdk-web name
their PAT secret DASH0_DOCS_REPO_GITHUB_PAT; the CLI uses DOCS_WEBSITE_PR_TOKEN. The operator keeps
its transformations.yaml at the repo-root .github/ while syncing from source-root: helm-chart/dash0-operator, so its transformations-file steps back up with ../../; the CLI and
sdk-web use the default source-root and file path. Match the caller's own conventions, not another
repo's.
Reference: the two modes
- Dry run (
dry-run: "true"): applies the transformations and the coverage check, then stops.
The target repo is not checked out, nothing is copied, and no PR is created. Needs none of the
target-side inputs and no token, so it is safe as a drift/coverage guard on PRs and non-release
builds.
- Full sync (
dry-run: "false", the default): runs end to end and opens or updates a PR in the
target repo. Requires the target-side inputs and a token.
Reference: inputs
| Input |
Required |
Default |
Notes |
source-root |
no |
. |
Root of the source repo whose docs are transformed. |
transformations-file |
no |
.github/workflows/sync-docs/transformations.yaml |
Relative to source-root. |
dry-run |
no |
"false" |
"true" → transform + coverage only, then stop. |
target-repository |
yes unless dry-run |
"" |
owner/name of the docs repo. Supply from a secret or variable, do not hardcode. |
target-directory |
yes unless dry-run |
"" |
Dir in the target repo the target: paths resolve under. Supply from a secret. |
target-github-token |
yes unless dry-run |
"" |
Fine-grained PAT on the target repo with contents:write + pull-requests:write. |
target-base-branch |
no |
main |
Branch the PR opens against. |
pr-branch |
yes unless dry-run |
"" |
Head branch for the sync PR. |
pr-title |
yes unless dry-run |
"" |
|
pr-body |
yes unless dry-run |
"" |
|
pr-reviewers |
no |
"" |
Comma-separated handles; only applied when a NEW PR is opened. |
pr-assignees |
no |
"" |
Comma-separated handles; only applied when a NEW PR is opened. |
commit-message |
no |
"" |
Falls back to pr-title when empty. |
The action validates the "required unless dry-run" inputs at runtime and fails with
::error::The following inputs are required unless dry-run is 'true': ... if any are missing, because
composite-action required: true is not enforced and which inputs are mandatory depends on dry-run.
Workflow A — wire up a sync in a source repo
Follow these steps in order. Do not skip the dry-run verification (step 6) before wiring the full sync.
Confirm the source docs and their intended target. List the docs the repo wants published
(README.md, docs/**) and, for each, the path it should occupy in the target repo relative to
target-directory. If the target repo, directory, or token secret names are unknown, ask the user
rather than guessing — these are caller-specific and must not be hardcoded.
Author transformations.yaml at .github/workflows/sync-docs/transformations.yaml (or another
path you will pass via transformations-file). Use Workflow B.
Resolve the action version to pin. Prefer a full commit SHA; a vX.Y.Z tag is acceptable when
the caller relies on Dependabot. Confirm the version is the current release so the caller gets the
no-defaults, dry-run, and coverage behavior described here.
Confirm the caller-side secrets exist in the source repo: the target repository, the target
directory, and the PAT. The recommended names are SYNC_DOCUMENTATION_TARGET_REPOSITORY,
SYNC_DOCUMENTATION_TARGET_DIRECTORY, and a contents:write + pull-requests:write PAT. If they do
not exist, tell the user which secrets to create and stop the full-sync wiring until they do.
Add the workflow file at .github/workflows/sync-docs.yml using the template below. One
invocation serves both modes; dry-run decides. For a real end-to-end example including the CI
wiring, copy from
dash0-operator's sync-docs-to-website.yaml
and the sync_docs_to_website* jobs in its
ci.yaml.
name: Synchronize docs to the Dash0 website
on:
workflow_dispatch:
inputs:
dry-run:
description: Only verify transformations + coverage; do not open a PR.
type: boolean
default: true
workflow_call:
inputs:
dry-run:
type: boolean
default: false
jobs:
sync-docs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: checkout source repo
uses: actions/checkout@v6
- name: sync docs to the Dash0 website
uses: dash0hq/sync-docs-action@<pinned-sha>
with:
dry-run: ${{ inputs.dry-run }}
target-repository: ${{ secrets.SYNC_DOCUMENTATION_TARGET_REPOSITORY }}
target-directory: ${{ secrets.SYNC_DOCUMENTATION_TARGET_DIRECTORY }}
target-github-token: ${{ secrets.REPOSITORY_FULL_ACCESS_GITHUB_TOKEN }}
pr-branch: sync-<source-repo>-docs
pr-title: "docs: synchronize <source-repo> documentation"
pr-body: |
Synchronizes the <source-repo> documentation into the Dash0 website.
Dry-run locally before pushing. Run Workflow C. Do not commit the workflow until the engine exits
0 and emits the expected files.
Add a drift guard. So transformation drift breaks CI early rather than on the next release sync,
invoke the same workflow with dry-run: true on pull requests. The dry run needs no token or target
inputs, so it is safe on forked-PR builds.
Report to the user which files were created, which secrets the workflow depends on, and how to
trigger a real sync (manual workflow_dispatch with dry-run unchecked, or a workflow_call from a
release workflow).
Facts that constrain this workflow:
dry-run: ${{ inputs.dry-run }} works because GitHub renders the boolean as the string
true/false, which the action compares against 'true'.
- The template has no
push: trigger for real syncs — syncs are deliberate. Only the dry-run guard
runs automatically.
- Target-side inputs are ignored during a dry run, so they can be supplied unconditionally.
Workflow B — author or edit transformations.yaml
The files: list is the sole opt-in allowlist; anything in the source repo not listed there is
ignored. Frontmatter (title/description + a lastUpdated timestamp) is generated from each entry and
prepended, so do not hand-write frontmatter in the source docs.
- Add one
files: entry per page to publish. Set source (relative to source-root), target
(relative to target-directory — do not repeat the target-directory prefix, and never start with
/), title, and description.
- Move rules shared by every file into
common:, in the order they should run. Per-file
transformations: run after common:.
- Add a
coverage: block if a newly added docs page should fail CI instead of being silently
skipped. Every file matching an include glob must appear as a files[].source or be listed under
ignore.
- Add a
nav: block only if the target section needs a generated nav.json.
- Verify with Workflow C after any edit.
For a flat sync with a coverage: guard, copy from
dash0-operator's transformations.yaml.
For a flat nav: block (one directory, no groupTitles), copy from
dash0-sdk-web's transformations.yaml.
For nested nav groups with groupTitles, copy from
dash0-cli's transformations.yaml.
common: # transformations applied to EVERY file, before per-file ones
- description: strip the leading top-level heading (the frontmatter title replaces it)
type: replace-regex
find: '^# [^\n]*\n'
replace: ""
coverage: # optional guard: every file matching include must be synced or ignored
include:
- docs/**/*.md
ignore: [] # exact source-relative paths intentionally not synced
nav: # optional: emit a nav.json describing the page hierarchy
target: <dir>/nav.json
id: <slug>
title: <Section title>
order: 72.6 # finite number
parentPath: Tooling # optional
groupTitles: # optional: title for each nested subdirectory
github-actions: GitHub Actions
files: # the opt-in allowlist — one entry per page to publish, anything not listed is ignored
# 1. A page that needs per-file fixups the other pages do not. `transformations:` holds edits that
# apply to THIS file only, running after every `common:` transformation. Use it for content that
# exists in one source file: repo-only links, badges, intro lines that do not belong on the website.
- source: README.md
target: <dir>/overview.md
title: Overview
description: What this project is and how to get started.
transformations:
- description: strip the CI/license badges at the top of the README
type: replace-regex
find: '^\[!\[[^\n]*\n'
replace: ""
flags:
- multiline
- description: rewrite a repo-relative link that has no page on the website
type: replace-regex
find: '\]\(CONTRIBUTING\.md\)'
replace: "](https://github.com/dash0hq/<repo>/blob/main/CONTRIBUTING.md)"
- description: drop the "This repository contains ..." intro line
type: remove-line
line: "This repository contains the source for the widget."
# 2. A page that needs no per-file edits. Omit `transformations:` entirely; only `common:` runs on it.
- source: docs/installation.md
target: <dir>/installation.md
title: Installation
description: Install and configure the project.
# 3. Another edit-free page. Relative sibling links between synced pages are rewritten automatically
# (the .md suffix is dropped), so no per-file transformation is needed just to fix links.
- source: docs/configuration.md
target: <dir>/configuration.md
title: Configuration
description: Reference for every configuration option.
transformations: on a files: entry is optional and per-file. It is a list of prepend /
replace-regex / remove-line edits applied to that one source file, in order, after the shared
common: transformations. Reach for it when an edit is specific to a single page — a badge only the
README carries, a repo-relative link that has no website equivalent, a heading that duplicates the
generated frontmatter title. Edits every page needs belong in common: instead; links between synced
pages are already rewritten automatically, so do not add per-file transformations just for those.
Transformation types:
prepend — insert content at the start of the document.
replace-regex — replace matches of find with replace. Optional flags: multiline
(^/$ match line boundaries), dotall (. matches newlines), ignorecase. By default a
replace-regex must match at least once or the run fails; set required: false to allow zero matches.
remove-line — remove the whole line containing the literal marker line.
The only supported placeholder in inserted or replacement text is $timestamp (one UTC value per run).
Nav generation: nav.json is derived from the on-disk hierarchy of the target paths. Files sharing
the common directory prefix become top-level leaves; files in a deeper subdirectory nest inside a
{ title, children } group whose title comes from groupTitles[<subdir slug>].
Workflow C — verify before pushing
Run the transformation engine directly against the docs — the same invocation the action makes. This
requires a checkout of sync-docs-action; run it from that repo's root.
Install the engine's dependencies:
pnpm install --frozen-lockfile
Run the transformer against the source repo (Node 24 from .nvmrc, pnpm 10):
node packages/transformation-engine/src/apply-transformations.ts <source-root> <source-root>/.github/workflows/sync-docs/transformations.yaml /tmp/transformed-docs
Check the result. Exit 0 with the expected files under /tmp/transformed-docs (including
nav.json when nav: is set) means the transforms and coverage check pass. A non-zero exit means
a transformation or coverage rule failed; go to Workflow E. The full sync only adds the target
checkout and PR on top of this.
Workflow D — migrate a caller to a newer action version
Callers pin to a SHA or a vX.Y.Z tag. When bumping across a release boundary, apply the changes for
every version crossed, in order. Steps are cumulative: bumping from v0.1.0 to the current release means
doing all of D3, then D2, then D1.
- Find the caller's current pin and map it to a version. Determine the lowest version boundary the
bump crosses, then apply the steps below from oldest to newest.
- D3 — leaving v0.1.0 (to v0.2.0 behavior):
- The built-in docs-coverage check was removed. In v0.1.0 a docs page with no sync entry broke the
workflow; afterward
files: is the sole opt-in list and undeclared files are silently ignored. If
the caller relied on that guard, add a coverage: block (see D1) once the bump is complete.
nav: is new and optional. Add it only if the caller wants a generated nav.json.
- D2 — leaving v0.2.0 (to v0.3.0 behavior): No caller changes required. v0.3.0 adds nested nav
groups derived from the
target path hierarchy plus an optional nav.groupTitles map. A
transformations.yaml whose files all land in one directory keeps emitting the same flat one-group
nav.json. Adopt groupTitles only when nesting files in subdirectories below the common prefix.
- D1 — reaching the current release (from v0.3.0 or earlier) — BREAKING:
target-repository and
target-directory no longer have defaults. Earlier versions defaulted them to Dash0's website
repo and its internal docs path; the action is public, so those defaults leaked a private repo's name
and layout. A full sync now fails fast with
::error::The following inputs are required unless dry-run is 'true': ... if either is missing.
- Update the pin to the target SHA or tag and run Workflow C to confirm the transforms still
apply.
- Report the required caller-side changes (new secrets, edited inputs) so the user can make them
before the next real sync.
Workflow E — diagnose a failing run
Match the symptom, apply the fix, then re-verify with Workflow C.
The following inputs are required unless dry-run is 'true': ... — a full sync ran without a
target-side input (commonly target-directory). Supply it, or run with dry-run: true.
- Coverage failure (
coverage check failed — the following files match coverage.include but have no 'files:' entry) — add a files: entry for the listed file, or list it under coverage.ignore.
replace-regex matched zero times — the source text changed (drift). Fix the find, or set
required: false if a no-op is acceptable.
- "documentation is already up to date, nothing to do" — not an error. The action diffs the target
dir ignoring the
lastUpdated: line; if nothing else changed it skips the PR. Change a doc to force
one.
- Existing PR "updated by the force-push" — the action reuses
pr-branch and force-pushes, so
repeat runs update the same PR rather than opening new ones. pr-reviewers/pr-assignees apply
only when a brand-new PR is opened.
- Token or PAT errors at
checkout target repository or create pull request — the PAT must be
scoped to the target repo with contents:write + pull-requests:write, and stored as a secret in
the source repo (where the workflow runs). For an org-owned target, a fine-grained token may need
org approval.
1---2name: dash0-sync-docs3description: Wire up or debug a source repository's docs sync to the Dash0 website via the dash0hq/sync-docs-action composite action. Use when adding a sync-docs-to-website workflow, authoring or editing a transformations.yaml (files/common/nav/coverage), configuring the dry-run vs full-sync modes, migrating a caller across action versions, or diagnosing a failing sync run (missing inputs, coverage failures, no-op PRs, PAT/token errors).4---56# Using dash0hq/sync-docs-action78`dash0hq/sync-docs-action` is a composite GitHub Action that transforms documentation in a **source9repository** (e.g. `dash0hq/otel-cicd-action`) according to that repo's `transformations.yaml`, then10opens or updates a pull request in a **target documentation repository** (Dash0's website repo). The11caller checks out its own repo, then invokes this action; the transform and PR flow live in the action12so every source repo shares one implementation.1314This skill is a set of workflows. Identify the task, then follow the matching workflow top to bottom.1516- **Wiring up a new sync** in a source repo → Workflow A.17- **Authoring or editing `transformations.yaml`** → Workflow B.18- **Verifying changes before pushing** → Workflow C.19- **Migrating a caller to a newer action version** → Workflow D.20- **Diagnosing a failing run** → Workflow E.2122## Reference: worked examples in production2324Three Dash0 repos consume this action. Read them before wiring or editing a caller — they are the25canonical, working shapes to copy from.2627- **`dash0hq/dash0-operator`** — a **flat sync** (all pages land as siblings under one directory) with a28 `coverage:` guard, pinned past the breaking release so it passes the target inputs from secrets.29 - [`.github/workflows/sync-docs-to-website.yaml`](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/sync-docs-to-website.yaml)30 — the caller workflow, one invocation for both modes.31 - [`.github/workflows/ci.yaml`](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/ci.yaml)32 — how CI invokes it: the `sync_docs_to_website_dry_run` job runs `dry-run: true` on every non-tag33 build, and `sync_docs_to_website` runs the full sync only after a release tag publishes.34 - [`.github/workflows/sync-docs/transformations.yaml`](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/sync-docs/transformations.yaml)35 — `common` + per-file `transformations`, a `coverage:` block, no `nav:`.36- **`dash0hq/dash0-cli`** — a **nested-nav sync** that groups pages sitting in a `github-actions/`37 subdirectory via `nav.groupTitles`. Still pinned to `v0.3.0`, so it is also a live example of a caller38 that has **not yet done the D1 migration** (its target coordinates still rely on the old defaults).39 - [`.github/workflows/sync-docs-to-website.yaml`](https://github.com/dash0hq/dash0-cli/blob/main/.github/workflows/sync-docs-to-website.yaml)40 — the caller workflow.41 - [`.github/workflows/sync-docs/transformations.yaml`](https://github.com/dash0hq/dash0-cli/blob/main/.github/workflows/sync-docs/transformations.yaml)42 — a `nav:` block with `groupTitles`, files nesting into `github-actions/`.43- **`dash0hq/dash0-sdk-web`** — a **flat `nav:` sync** (a `nav:` block, but every page lands directly44 under one directory, so no `groupTitles`), pinned past the breaking release with `pr-reviewers` set.45 Still on a feature branch, so link to the `add-sync-docs-to-website` branch, not `main`.46 **Re-point these two links to `main` once the `add-sync-docs-to-website` PR merges**; the branch links47 break when that branch is deleted.48 - [`.github/workflows/sync-docs-to-website.yaml`](https://github.com/dash0hq/dash0-sdk-web/blob/add-sync-docs-to-website/.github/workflows/sync-docs-to-website.yaml)49 — the caller workflow (default `source-root` and `transformations-file`).50 - [`.github/workflows/sync-docs/transformations.yaml`](https://github.com/dash0hq/dash0-sdk-web/blob/add-sync-docs-to-website/.github/workflows/sync-docs/transformations.yaml)51 — `README.md` → `overview.md`, `INSTALL.md` → `installation.md`, `docs/sdk/*.md` → sibling pages, all52 under `web-sdk/`, with a flat `nav:` block.5354These repos deliberately differ in details you must not copy blindly. The operator and sdk-web name55their PAT secret `DASH0_DOCS_REPO_GITHUB_PAT`; the CLI uses `DOCS_WEBSITE_PR_TOKEN`. The operator keeps56its `transformations.yaml` at the repo-root `.github/` while syncing from `source-root:57helm-chart/dash0-operator`, so its `transformations-file` steps back up with `../../`; the CLI and58sdk-web use the default `source-root` and file path. Match the caller's own conventions, not another59repo's.6061## Reference: the two modes6263- **Dry run** (`dry-run: "true"`): applies the transformations and the coverage check, then **stops**.64 The target repo is not checked out, nothing is copied, and no PR is created. Needs **none** of the65 target-side inputs and **no token**, so it is safe as a drift/coverage guard on PRs and non-release66 builds.67- **Full sync** (`dry-run: "false"`, the default): runs end to end and opens or updates a PR in the68 target repo. Requires the target-side inputs and a token.6970## Reference: inputs7172| Input | Required | Default | Notes |73| ---------------------- | ---------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------- |74| `source-root` | no | `.` | Root of the source repo whose docs are transformed. |75| `transformations-file` | no | `.github/workflows/sync-docs/transformations.yaml` | Relative to `source-root`. |76| `dry-run` | no | `"false"` | `"true"` → transform + coverage only, then stop. |77| `target-repository` | **yes unless dry-run** | `""` | `owner/name` of the docs repo. Supply from a secret or variable, do not hardcode. |78| `target-directory` | **yes unless dry-run** | `""` | Dir in the target repo the `target:` paths resolve under. Supply from a secret. |79| `target-github-token` | **yes unless dry-run** | `""` | Fine-grained PAT on the target repo with `contents:write` + `pull-requests:write`. |80| `target-base-branch` | no | `main` | Branch the PR opens against. |81| `pr-branch` | **yes unless dry-run** | `""` | Head branch for the sync PR. |82| `pr-title` | **yes unless dry-run** | `""` | |83| `pr-body` | **yes unless dry-run** | `""` | |84| `pr-reviewers` | no | `""` | Comma-separated handles; only applied when a NEW PR is opened. |85| `pr-assignees` | no | `""` | Comma-separated handles; only applied when a NEW PR is opened. |86| `commit-message` | no | `""` | Falls back to `pr-title` when empty. |8788The action validates the "required unless dry-run" inputs at runtime and fails with89`::error::The following inputs are required unless dry-run is 'true': ...` if any are missing, because90composite-action `required: true` is not enforced and which inputs are mandatory depends on `dry-run`.9192## Workflow A — wire up a sync in a source repo9394Follow these steps in order. Do not skip the dry-run verification (step 6) before wiring the full sync.95961. **Confirm the source docs and their intended target.** List the docs the repo wants published97 (`README.md`, `docs/**`) and, for each, the path it should occupy in the target repo relative to98 `target-directory`. If the target repo, directory, or token secret names are unknown, ask the user99 rather than guessing — these are caller-specific and must not be hardcoded.1002. **Author `transformations.yaml`** at `.github/workflows/sync-docs/transformations.yaml` (or another101 path you will pass via `transformations-file`). Use Workflow B.1023. **Resolve the action version to pin.** Prefer a full commit SHA; a `vX.Y.Z` tag is acceptable when103 the caller relies on Dependabot. Confirm the version is the current release so the caller gets the104 no-defaults, dry-run, and coverage behavior described here.1054. **Confirm the caller-side secrets exist** in the source repo: the target repository, the target106 directory, and the PAT. The recommended names are `SYNC_DOCUMENTATION_TARGET_REPOSITORY`,107 `SYNC_DOCUMENTATION_TARGET_DIRECTORY`, and a `contents:write` + `pull-requests:write` PAT. If they do108 not exist, tell the user which secrets to create and stop the full-sync wiring until they do.1095. **Add the workflow file** at `.github/workflows/sync-docs.yml` using the template below. One110 invocation serves both modes; `dry-run` decides. For a real end-to-end example including the CI111 wiring, copy from112 [dash0-operator's `sync-docs-to-website.yaml`](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/sync-docs-to-website.yaml)113 and the `sync_docs_to_website*` jobs in its114 [`ci.yaml`](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/ci.yaml).115116 ```yaml117 name: Synchronize docs to the Dash0 website118119 on:120 workflow_dispatch:121 inputs:122 dry-run:123 description: Only verify transformations + coverage; do not open a PR.124 type: boolean125 default: true126 workflow_call:127 inputs:128 dry-run:129 type: boolean130 default: false131132 jobs:133 sync-docs:134 runs-on: ubuntu-latest135 timeout-minutes: 10136 steps:137 - name: checkout source repo138 uses: actions/checkout@v6139140 - name: sync docs to the Dash0 website141 uses: dash0hq/sync-docs-action@<pinned-sha>142 with:143 dry-run: ${{ inputs.dry-run }}144 target-repository: ${{ secrets.SYNC_DOCUMENTATION_TARGET_REPOSITORY }}145 target-directory: ${{ secrets.SYNC_DOCUMENTATION_TARGET_DIRECTORY }}146 target-github-token: ${{ secrets.REPOSITORY_FULL_ACCESS_GITHUB_TOKEN }}147 pr-branch: sync-<source-repo>-docs148 pr-title: "docs: synchronize <source-repo> documentation"149 pr-body: |150 Synchronizes the <source-repo> documentation into the Dash0 website.151 ```1521536. **Dry-run locally before pushing.** Run Workflow C. Do not commit the workflow until the engine exits154 0 and emits the expected files.1557. **Add a drift guard.** So transformation drift breaks CI early rather than on the next release sync,156 invoke the same workflow with `dry-run: true` on pull requests. The dry run needs no token or target157 inputs, so it is safe on forked-PR builds.1588. **Report to the user** which files were created, which secrets the workflow depends on, and how to159 trigger a real sync (manual `workflow_dispatch` with dry-run unchecked, or a `workflow_call` from a160 release workflow).161162Facts that constrain this workflow:163164- `dry-run: ${{ inputs.dry-run }}` works because GitHub renders the boolean as the string165 `true`/`false`, which the action compares against `'true'`.166- The template has **no `push:` trigger for real syncs** — syncs are deliberate. Only the dry-run guard167 runs automatically.168- Target-side inputs are ignored during a dry run, so they can be supplied unconditionally.169170## Workflow B — author or edit transformations.yaml171172The `files:` list is the **sole opt-in allowlist**; anything in the source repo not listed there is173ignored. Frontmatter (`title`/`description` + a `lastUpdated` timestamp) is generated from each entry and174prepended, so do not hand-write frontmatter in the source docs.1751761. **Add one `files:` entry per page** to publish. Set `source` (relative to `source-root`), `target`177 (relative to `target-directory` — do **not** repeat the target-directory prefix, and never start with178 `/`), `title`, and `description`.1792. **Move rules shared by every file into `common:`**, in the order they should run. Per-file180 `transformations:` run after `common:`.1813. **Add a `coverage:` block** if a newly added docs page should fail CI instead of being silently182 skipped. Every file matching an `include` glob must appear as a `files[].source` or be listed under183 `ignore`.1844. **Add a `nav:` block** only if the target section needs a generated `nav.json`.1855. **Verify with Workflow C** after any edit.186187For a flat sync with a `coverage:` guard, copy from188[dash0-operator's `transformations.yaml`](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/sync-docs/transformations.yaml).189For a flat `nav:` block (one directory, no `groupTitles`), copy from190[dash0-sdk-web's `transformations.yaml`](https://github.com/dash0hq/dash0-sdk-web/blob/add-sync-docs-to-website/.github/workflows/sync-docs/transformations.yaml).191For nested nav groups with `groupTitles`, copy from192[dash0-cli's `transformations.yaml`](https://github.com/dash0hq/dash0-cli/blob/main/.github/workflows/sync-docs/transformations.yaml).193194```yaml195common: # transformations applied to EVERY file, before per-file ones196 - description: strip the leading top-level heading (the frontmatter title replaces it)197 type: replace-regex198 find: '^# [^\n]*\n'199 replace: ""200201coverage: # optional guard: every file matching include must be synced or ignored202 include:203 - docs/**/*.md204 ignore: [] # exact source-relative paths intentionally not synced205206nav: # optional: emit a nav.json describing the page hierarchy207 target: <dir>/nav.json208 id: <slug>209 title: <Section title>210 order: 72.6 # finite number211 parentPath: Tooling # optional212 groupTitles: # optional: title for each nested subdirectory213 github-actions: GitHub Actions214215files: # the opt-in allowlist — one entry per page to publish, anything not listed is ignored216 # 1. A page that needs per-file fixups the other pages do not. `transformations:` holds edits that217 # apply to THIS file only, running after every `common:` transformation. Use it for content that218 # exists in one source file: repo-only links, badges, intro lines that do not belong on the website.219 - source: README.md220 target: <dir>/overview.md221 title: Overview222 description: What this project is and how to get started.223 transformations:224 - description: strip the CI/license badges at the top of the README225 type: replace-regex226 find: '^\[!\[[^\n]*\n'227 replace: ""228 flags:229 - multiline230 - description: rewrite a repo-relative link that has no page on the website231 type: replace-regex232 find: '\]\(CONTRIBUTING\.md\)'233 replace: "](https://github.com/dash0hq/<repo>/blob/main/CONTRIBUTING.md)"234 - description: drop the "This repository contains ..." intro line235 type: remove-line236 line: "This repository contains the source for the widget."237238 # 2. A page that needs no per-file edits. Omit `transformations:` entirely; only `common:` runs on it.239 - source: docs/installation.md240 target: <dir>/installation.md241 title: Installation242 description: Install and configure the project.243244 # 3. Another edit-free page. Relative sibling links between synced pages are rewritten automatically245 # (the .md suffix is dropped), so no per-file transformation is needed just to fix links.246 - source: docs/configuration.md247 target: <dir>/configuration.md248 title: Configuration249 description: Reference for every configuration option.250```251252`transformations:` on a `files:` entry is **optional and per-file**. It is a list of `prepend` /253`replace-regex` / `remove-line` edits applied to that one source file, in order, **after** the shared254`common:` transformations. Reach for it when an edit is specific to a single page — a badge only the255README carries, a repo-relative link that has no website equivalent, a heading that duplicates the256generated frontmatter title. Edits every page needs belong in `common:` instead; links between synced257pages are already rewritten automatically, so do not add per-file transformations just for those.258259Transformation types:260261- **`prepend`** — insert `content` at the start of the document.262- **`replace-regex`** — replace matches of `find` with `replace`. Optional `flags`: `multiline`263 (`^`/`$` match line boundaries), `dotall` (`.` matches newlines), `ignorecase`. By default a264 `replace-regex` must match at least once or the run fails; set `required: false` to allow zero matches.265- **`remove-line`** — remove the whole line containing the literal marker `line`.266267The only supported placeholder in inserted or replacement text is `$timestamp` (one UTC value per run).268269Nav generation: `nav.json` is derived from the on-disk hierarchy of the `target` paths. Files sharing270the common directory prefix become top-level leaves; files in a deeper subdirectory nest inside a271`{ title, children }` group whose title comes from `groupTitles[<subdir slug>]`.272273## Workflow C — verify before pushing274275Run the transformation engine directly against the docs — the same invocation the action makes. This276requires a checkout of `sync-docs-action`; run it from that repo's root.2772781. Install the engine's dependencies:279280 ```bash281 pnpm install --frozen-lockfile282 ```2832842. Run the transformer against the source repo (Node 24 from `.nvmrc`, pnpm 10):285286 ```bash287 node packages/transformation-engine/src/apply-transformations.ts <source-root> <source-root>/.github/workflows/sync-docs/transformations.yaml /tmp/transformed-docs288 ```2892903. **Check the result.** Exit 0 with the expected files under `/tmp/transformed-docs` (including291 `nav.json` when `nav:` is set) means the transforms and coverage check pass. A non-zero exit means292 a transformation or coverage rule failed; go to Workflow E. The full sync only adds the target293 checkout and PR on top of this.294295## Workflow D — migrate a caller to a newer action version296297Callers pin to a SHA or a `vX.Y.Z` tag. When bumping across a release boundary, apply the changes for298every version crossed, in order. Steps are cumulative: bumping from v0.1.0 to the current release means299doing all of D3, then D2, then D1.3003011. **Find the caller's current pin** and map it to a version. Determine the lowest version boundary the302 bump crosses, then apply the steps below from oldest to newest.3032. **D3 — leaving v0.1.0 (to v0.2.0 behavior):**304 - The built-in docs-coverage check was removed. In v0.1.0 a docs page with no sync entry broke the305 workflow; afterward `files:` is the sole opt-in list and undeclared files are silently ignored. If306 the caller relied on that guard, add a `coverage:` block (see D1) once the bump is complete.307 - `nav:` is new and optional. Add it only if the caller wants a generated `nav.json`.3083. **D2 — leaving v0.2.0 (to v0.3.0 behavior):** No caller changes required. v0.3.0 adds nested nav309 groups derived from the `target` path hierarchy plus an optional `nav.groupTitles` map. A310 `transformations.yaml` whose files all land in one directory keeps emitting the same flat one-group311 `nav.json`. Adopt `groupTitles` only when nesting files in subdirectories below the common prefix.3124. **D1 — reaching the current release (from v0.3.0 or earlier) — BREAKING:** `target-repository` and313 `target-directory` **no longer have defaults**. Earlier versions defaulted them to Dash0's website314 repo and its internal docs path; the action is public, so those defaults leaked a private repo's name315 and layout. A full sync now fails fast with316 `::error::The following inputs are required unless dry-run is 'true': ...` if either is missing.317 - **Supply both inputs explicitly**, from a secret or variable, never hardcoded in a public workflow:318 ```yaml319 target-repository: ${{ secrets.SYNC_DOCUMENTATION_TARGET_REPOSITORY }}320 target-directory: ${{ secrets.SYNC_DOCUMENTATION_TARGET_DIRECTORY }}321 ```322 - If the caller relied on the old defaults, create those secrets with the previous values, then wire323 the inputs to them.324 - [dash0-cli](https://github.com/dash0hq/dash0-cli/blob/main/.github/workflows/sync-docs-to-website.yaml)325 is a caller still pinned to `v0.3.0` that has not done this migration yet;326 [dash0-operator](https://github.com/dash0hq/dash0-operator/blob/main/.github/workflows/sync-docs-to-website.yaml)327 is one that has, and shows the finished shape (target inputs supplied from secrets).328 - The rest of this release is optional and backward-compatible: `dry-run`, `coverage:`,329 `pr-reviewers`, `pr-assignees`. Adopt `dry-run: true` on non-release CI to catch drift early, and330 add a `coverage:` block to fail on newly added, unsynced docs pages.3315. **Update the pin** to the target SHA or tag and **run Workflow C** to confirm the transforms still332 apply.3336. **Report** the required caller-side changes (new secrets, edited inputs) so the user can make them334 before the next real sync.335336## Workflow E — diagnose a failing run337338Match the symptom, apply the fix, then re-verify with Workflow C.339340- **`The following inputs are required unless dry-run is 'true': ...`** — a full sync ran without a341 target-side input (commonly `target-directory`). Supply it, or run with `dry-run: true`.342- **Coverage failure** (`coverage check failed — the following files match coverage.include but have no343'files:' entry`) — add a `files:` entry for the listed file, or list it under `coverage.ignore`.344- **`replace-regex` matched zero times** — the source text changed (drift). Fix the `find`, or set345 `required: false` if a no-op is acceptable.346- **"documentation is already up to date, nothing to do"** — not an error. The action diffs the target347 dir ignoring the `lastUpdated:` line; if nothing else changed it skips the PR. Change a doc to force348 one.349- **Existing PR "updated by the force-push"** — the action reuses `pr-branch` and force-pushes, so350 repeat runs **update the same PR** rather than opening new ones. `pr-reviewers`/`pr-assignees` apply351 only when a brand-new PR is opened.352- **Token or PAT errors at `checkout target repository` or `create pull request`** — the PAT must be353 scoped to the **target** repo with `contents:write` + `pull-requests:write`, and stored as a secret in354 the **source** repo (where the workflow runs). For an org-owned target, a fine-grained token may need355 org approval.