> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aviatrix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BUILD

# Build & Publish Pipeline

How a snippet edit becomes a PR preview and, once merged, the live page at
docs.aviatrix.com. For the snippet → shell → nav generation internals, see
[VERSIONING.md](VERSIONING.md). For local build commands, see the
[Quick Start](README.md#quick-start) and
[🔧 Build System](README.md#-build-system) sections of `README.md`.

***

## Architecture: source vs. generated

This repo holds only **source**: snippets, navigation templates, and the Python
generators. The build output — `docs.json`, the `docs/enterprise/{8.2,9.0}/`
shells, and `navigation-parser/subsections/generated/` — is git-ignored and
never committed on `main` or a feature branch. It exists locally only after you
run `make all`, and in CI only for the duration of a workflow run.

Instead, a separate workflow (below) rebuilds that output fresh on every push
and force-pushes it to a dedicated **deploy branch** that Mintlify actually
serves from. Source branches and deploy branches are kept strictly separate:

| Branch type | Contains | Who reads it |
| - | - | - |
| `main` / feature branches | Snippets, generators, scripts, CI config — no build output | Contributors, CI validation |
| `main-prod` | Generated `docs.json` + shells + snippets/images/index.mdx (source-only dirs pruned) | Mintlify (production, docs.aviatrix.com) |
| `preview-<branch>` | Same as `main-prod`, built from a PR's head branch | Mintlify (per-PR preview) |

***

## CI: validation (`build-docs.yml`)

Runs on every pull request (any branch) and every push to `main`/`master`.
Purely a correctness gate — it never publishes anything.

1. `make clean && make all` — must produce a complete `docs.json`.
2. `python3 scripts/check_version_gaps.py` — a page missing from a newer version
   must be documented by a `remove` entry in
   `navigation-parser/subsections/overrides/`.
3. `python3 scripts/check_nav_shells.py` — every nav entry resolves to an actual
   generated shell file.
4. `python3 scripts/lint_docs.py` — frontmatter, `{props.X}` references,
   version-templated doc links, absolute image paths.
5. `python3 scripts/check_image_duplicates.py` — no duplicate image assets.
6. `python3 scripts/check_links_images.py` — internal links and image references
   resolve.
7. `python3 -m pytest scripts/tests/` — generator unit tests.
8. A final check that `docs.json` exists, is valid JSON, and contains the
   expected `navigation`/`name` structure.

On failure, the image/link checks post a summary to the job's
`GITHUB_STEP_SUMMARY`.

***

## CD: publish (`publish-mint-deploy.yml`)

Rebuilds and force-pushes the servable tree to a deploy branch. Routing:

* **Push to `main`** → `main-prod` (production; serves docs.aviatrix.com).
* **Pull request against any base except `main-prod`**
  (opened/synchronize/reopened) → `preview-<feature-branch>` (slashes in the
  branch name become `-`), plus a preview URL. Release-cycle bases such as
  `v10.1-dev` are included.
* **`workflow_dispatch`** with a `target_override` input → an arbitrary branch
  name. Rarely needed now that every PR is covered; useful for previewing a
  branch that has no PR open yet.

Previews are fully automatic and refresh on every push to the PR. Nothing has to
be run by hand and nothing has to be registered in the Mintlify dashboard — see
[Getting a preview URL](#getting-a-preview-url-preview-branch-is-not-enough).

`main-prod` is excluded from the `pull_request` trigger on purpose: it is the
production deploy target, not a base branch contributors PR against.

Steps:

1. Check out the PR's head ref (or the pushed ref).
2. `make all` — regenerate `docs.json` and the shells.
3. `rsync` the working tree into `/tmp/deploy`, excluding everything Mintlify
   does not need to serve (`scripts/`, `navigation-parser/`, `Makefile`,
   `.github/`, `.husky/`, lint/format configs, `node_modules/`, `package*.json`,
   `*.py`, and the source `.gitignore` itself — excluding the `.gitignore`
   matters, otherwise `git add -A` on the deploy branch would silently drop the
   generated output it's supposed to ship).
4. Force-push that pruned tree as a single commit to the target branch
   (`preview-<feature-branch>`, or `main-prod` for a production push).
5. On a PR event, call Mintlify's `POST /project/preview/{projectId}` API
   directly (with 429-backoff retry, up to 4 attempts) to trigger a build for
   the pushed branch and get back a `statusId` and `previewUrl`. This step
   checks `MINTLIFY_API_KEY` / `MINTLIFY_PROJECT_ID` are set first and skips
   cleanly if not — see
   [Getting a preview URL](#getting-a-preview-url-preview-branch-is-not-enough)
   for why the explicit call is necessary at all.
6. Poll `GET /project/update-status/{statusId}` every 10 seconds, up to a
   5-minute deadline. The loop breaks as soon as `status` is any non-empty value
   other than the documented in-progress states `queued`/`in_progress` — not
   only on an exact `success`/`failure` match — and reports that raw value. This
   is hardening for an undocumented status Mintlify has been observed to return
   outside its stated `queued`/`in_progress`/`success`/`failure` enum (see
   [Getting a preview URL](#getting-a-preview-url-preview-branch-is-not-enough));
   without it, that case would loop silently for the full timeout instead of
   being recognized.
7. Upsert a single marked comment (an HTML comment containing the string
   `mintlify-preview`) on the source PR with the outcome: "ready" with the
   preview link on `success`, a build-failure message (with Mintlify's
   `summary`) on `failure`, a "build failed to start" message if the trigger
   call itself failed, a distinct "Mintlify reported an unusual status" message
   for any other terminal-looking value (e.g. `skipped`), a "still building"
   note if the deadline passed with no terminal status, or a "credentials not
   configured" note if `MINTLIFY_API_KEY` / `MINTLIFY_PROJECT_ID` are unset —
   plus the source SHA. Upserting rather than posting once keeps the comment
   accurate across pushes.

The Mintlify dashboard is configured with `main-prod` as the production deploy
source; that is what actually serves docs.aviatrix.com.

### Getting a preview URL (`preview-<branch>` is not enough)

**Nothing to do — open a PR and the URL is posted as a comment.** This section
explains the mechanism, because the moving part is not obvious.

Force-pushing `preview-<branch>` builds the servable tree, but Mintlify's
PR-preview feature only fires for pull requests targeting the project's
configured deployment branch (`main-prod`), and nobody opens PRs against that
branch. So this workflow calls Mintlify's preview-trigger API directly
(`POST /project/preview/{projectId}`) right after the force-push, to get back a
`statusId`/`previewUrl` it can poll and post. That explicit call has been proven
live multiple times to be what actually provisions a live, publicly-routable
preview site.

**Known, accepted tradeoff: this causes a duplicate build on every push.**
Force-pushing `preview-<branch>` *also* triggers an automatic Mintlify build on
its own, via its GitHub App (observed in the dashboard as a build authored by
the workflow's `shell-builder` commit identity), independent of the explicit
trigger call above. Every push therefore produces two builds. A rework was tried
to eliminate this: drop the explicit trigger, rely solely on the automatic
push-triggered build, and construct the preview URL deterministically
(`https://aviatrix-14b37c43-<target-branch>.mintlify.site`) rather than reading
it from the trigger response, polling that URL for HTTP 200 instead of a
`statusId`. **That rework was reverted** after a live smoke test: a branch that
only got the automatic build returned a fresh 404 at the constructed URL on
repeated, independently-verified curls — the automatic build alone does not
reliably (or does not at all) produce a publicly-routable site. Do not
reintroduce the shell-builder-only / URL-construction approach without solving
that gap first; the duplicate-build cost is accepted as the price of a preview
that actually works.

The mechanism, as restored:

1. Trigger: `POST /project/preview/{projectId}` with the pushed branch name,
   retrying on HTTP 429 with backoff (up to 4 attempts). The response's
   `statusId` and `previewUrl` are captured as step outputs.
2. Poll: `GET /project/update-status/{statusId}` every 10 seconds, up to a
   5-minute deadline, until `status` is anything other than the documented
   in-progress states `queued`/`in_progress`.
3. **Hardening added on this pass:** the poll loop used to only break on an
   exact `success`/`failure` match. In one live test, the triggered build was
   marked `"Skipped — Superseded by a newer commit"` — an undocumented status
   outside the API's stated `queued`/`in_progress`/`success`/`failure` enum —
   and the old loop ran out its full 5-minute timeout without recognizing it.
   The loop now breaks on any non-empty, non-in-progress status and reports it
   as-is; the PR comment renders a distinct "Mintlify reported an unusual
   status" message for anything other than `success`/`failure`, instead of
   misreporting it as "still building".
4. The workflow upserts a single marked comment on the PR with the outcome and
   the source SHA.

`MINTLIFY_API_KEY` and `MINTLIFY_PROJECT_ID` repo secrets are checked before the
trigger call is made at all — unset either to skip the trigger/poll/comment
entirely (a deliberate opt-out knob), not just to suppress a comment for calls
that already happened.

**Manual fallback.** `scripts/preview-branch.sh` runs the same build →
force-push → API-trigger → poll sequence for a branch that has no open PR (so
the automatic push-triggered build is the only other thing that fires), or to
force-refresh a stale preview. It was not touched by the URL-reachability rework
or this revert, and its trigger/poll mechanism already matches
`publish-mint-deploy.yml` as restored above.

```bash theme={null}
scripts/preview-branch.sh                      # current branch
scripts/preview-branch.sh AVX-12345-my-branch  # any branch
scripts/preview-branch.sh my-branch --force    # rebuild even if current
npm run preview -- my-branch                   # same thing via npm
```

The `/refresh-preview [branch]` slash command wraps it. Requires `gh`
(authenticated with `workflow` scope) plus the same `MINTLIFY_API_KEY` /
`MINTLIFY_PROJECT_ID` credentials the CI workflow uses.

### Cleanup (`cleanup-preview-branch.yml`)

When a PR closes (merged or not), this workflow deletes its `preview-<branch>`.
It is a no-op if the branch was never created (for example, a PR with no source
changes).

***

## PDF generation

Not part of CI — run locally, and requires
[PrinceXML](https://www.princexml.com/) (`brew install --cask prince`):

```bash theme={null}
make pdf-enterprise        # current Enterprise version
make pdf-enterprise-8.2    # Enterprise 8.2
make pdf-enterprise-8.1    # Enterprise 8.1
make pdf-cloud             # Cloud documentation
make pdf-all               # all of the above
```

Each target starts a temporary local `mintlify dev` server, extracts the
relevant page URLs from `docs.json`, renders them with `prince`, and tears the
server down.

***

## See also

* [README.md](README.md) — repo layout, local dev quick start.
* [VERSIONING.md](VERSIONING.md) — snippet → shell → nav generation internals,
  per-version authoring patterns.
* [CONTRIBUTING.md](CONTRIBUTING.md) — editing standards, voice/style, branching
  conventions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.