Skip to content

<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /guides/DocsSite.md for this page in Markdown format</div>

Documentation Site

The Ecommercen docs site is built with VitePress. It bundles API guides, business flow documentation, the wiki, changelogs, and interactive OpenAPI references into a single static site. Each client gets their own docs site hosted on Cloudflare Pages.

Architecture

docs-gen/                         ← VitePress project (build tooling)
├── .vitepress/
│   ├── config.mts                ← Site config, nav, sidebar, OpenAPI sidebar generation
│   └── theme/index.ts            ← Theme setup, vitepress-openapi registration
├── specs.ts                      ← OpenAPI spec registry
├── create-spec-routes.ts         ← Generates dynamic route dirs for each spec
└── package.json                  ← Separate from root — has its own node_modules

docs/                             ← Content (markdown source)
├── api-guides/                   ← REST API usage guides (auth, pagination, filtering…)
├── changelog/                    ← Version changelogs (Changelog.4.99.md, etc.)
├── flows/                        ← Business logic flow docs (CF-*, AD-*, SY-*, IN-*)
├── guides/                       ← Wiki — architecture, patterns, integrations, this page
└── operations/                   ← Auto-generated OpenAPI operation pages

docs-gen/dist/                    ← Build output (gitignored, ~500MB)

docs-gen/ has its own package.json because the root project enforces Node ~20.16.0 and VitePress needs a freer range.

Hosting on Cloudflare Pages

Each client gets a CF Pages project (docs-{client}-internal) with a custom domain (docs-{client}-internal.ecommercen.com). The upstream repo uses docs-upstream-internal.ecommercen.com. Access is restricted to @ecommercen.com emails via a single wildcard Zero Trust policy.

One-time account setup

Install wrangler and authenticate:

bash
npm install -g wrangler
wrangler login

Create the wildcard Zero Trust application (covers all *.ecommercen.com subdomains — do this once, never again):

bash
# Create the Access application
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "self_hosted",
    "domain": "docs-*-internal.ecommercen.com",
    "name": "Ecommercen Docs (Internal)",
    "session_duration": "24h"
  }'

# Create the email domain policy (note the app ID from the response above)
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps/${APP_ID}/policies" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "allow",
    "name": "Advisable team",
    "include": [{ "email_domain": { "domain": "ecommercen.com" } }]
  }'

After this, any *.ecommercen.com subdomain shows a CF login page. Users enter their @ecommercen.com email, receive a one-time PIN, and they're in.

Set these as workspace-level CI variables (Bitbucket or GitHub — shared across all client repos):

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID

New client

The pipeline handles everything automatically. On first deploy, it creates the CF Pages project if it doesn't exist. The only manual step is adding the custom domain:

bash
# Add custom domain (one-time per client)
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/docs-${CLIENT}-internal/domains" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{ "name": "docs-'${CLIENT}'-internal.ecommercen.com" }'

Zero Trust is already active from the wildcard policy — no per-client setup needed.

Pipeline

The pipeline (bitbucket-pipelines.yml) has three trigger types:

Release (*.*.* tag) — production deploy, runs in parallel with Docker image build:

  • Upstream tag (4.99.6) → deploys to docs-upstream-internal
  • Client tag (4.99.6.1.wecare) → deploys to docs-wecare-internal

PR open/update — preview deploy, runs in parallel with PR Docker image build:

  • Upstream PR #42 → deploys to docs-upstream-pr42-internal
  • Client PR #42 → deploys to docs-wecare-pr42-internal
  • Docker image tagged as upstream-pr42 / wecare-pr42

PR merged/declined — automatic cleanup:

  • Deletes the CF Pages preview project
  • Deletes the PR Docker Hub tag

CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are Bitbucket workspace-level variables. If not set, the docs step skips gracefully — Docker build is unaffected.

Once repos move to GitHub, the tag/PR triggers can be replaced with native CF Pages git integration.

Delete a client's docs

bash
npx wrangler pages project delete "docs-${CLIENT}-internal"

DNS record is auto-cleaned if the domain is on Cloudflare. The wildcard Zero Trust policy doesn't need cleanup — it just stops matching once the subdomain is gone.

Manual deploy (without CI)

bash
cd docs-gen
npm ci && npm run build
npx wrangler pages deploy dist --project-name="${CLIENT}-docs-ecommercen"

Running locally

bash
cd docs-gen
npm install          # first time only
npm run dev          # starts at http://localhost:5173

Hot-reloads markdown changes. API reference pages are generated from public/openapi.json on startup.

Building locally

bash
cd docs-gen
npm run build

Outputs to docs-gen/dist/. Runs Pagefind for full-text search indexing after VitePress build. Needs ~8GB Node memory (configured via NODE_OPTIONS in the script).

Content sections

SectionPathSidebar keySource of truth
API Guidesdocs/api-guides//api-guides/Hand-written markdown
Business Flowsdocs/flows//flows/doc-ba pipeline (agent-assisted)
API Referencedocs/operations//operations/Generated from public/openapi.json
Wikidocs/guides//guides/Hand-written markdown
Changelogdocs/changelog//changelog/docs-updater agent or manual

Adding a page: create the .md file, add it to the sidebar in docs-gen/.vitepress/config.mts.

Client-specific flow docs

Client repos inherit upstream flow docs in docs/flows/ via the fork. Client-specific features (code in custom/ and application/) get their own flow docs in a separate directory:

docs/client/flows/
├── index.md
├── customer/CCF-01-*.md
├── admin/CAD-01-*.md
├── system/CSY-01-*.md
└── integration/CIN-01-*.md

Client prefixes mirror upstream but with a C prefix:

SubdirectoryClient prefixUpstream equivalent
customer/CCF-NNCF-NN
admin/CAD-NNAD-NN
system/CSY-NNSY-NN
integration/CIN-NNIN-NN

Each prefix has its own numbering sequence. The doc-ba agents automatically use these paths and prefixes when REPO_TYPE=client.

In the main repo, docs/client/ is excluded from the VitePress build (srcExclude: ['client/**']). Client repos remove that exclusion and add a sidebar section:

ts
// In the client repo's docs-gen/.vitepress/config.mts, add to sidebar:
'/client/flows/': [
  {
    text: 'Client Flows',
    items: [
      { text: 'All Flows', link: '/client/flows/' },
    ],
  },
  {
    text: 'Client Admin',
    items: [
      { text: 'CAD-01 Certus Dashboard', link: '/client/flows/admin/CAD-01-certus-dashboard' },
    ],
  },
  {
    text: 'Client Integration',
    items: [
      { text: 'CIN-01 Certus API', link: '/client/flows/integration/CIN-01-certus-api' },
    ],
  },
],

And add a nav entry:

ts
nav: [
  // ... existing entries
  { text: 'Client Flows', link: '/client/flows/' },
],

OpenAPI multi-spec

The site supports multiple OpenAPI specs via a registry. Each spec gets its own sidebar section, nav entry, and operation pages.

How it works

  1. specs.ts — registry listing every spec (id, name, file path, URL prefix)
  2. config.mts — static imports + sidebar generation per spec via useSidebar({ spec, linkPrefix })
  3. docs/{dir}/[operationId].md — per-page useOpenapi() with the spec, renders &lt;OAOperation>

The platform spec

public/openapi.json is generated via php cli.php job/GenerateOpenApiJson. The OpenAPI annotations live on REST controllers in src/Rest/.

Adding a client-specific spec

For client repos integrating with external APIs (Wallbid, ERP vendors, etc.):

bash
# 1. Place the OpenAPI 3.x JSON in public/ (must have operationId on every operation)
cp wallbid-api.json public/wallbid.json
ts
// 2. Add entry to docs-gen/specs.ts
export const specs: SpecEntry[] = [
  { id: 'platform', name: 'Platform API', ... },
  {
    id: 'wallbid',
    name: 'Wallbid API',
    file: '../../public/wallbid.json',
    dir: 'wallbid',
    navLabel: 'Wallbid API',
    config: { schemaDefaultView: 'schema' },
  },
]
ts
// 3. Add static import to docs-gen/.vitepress/config.mts
import wallbidSpec from '../../public/wallbid.json' with { type: 'json' }

const specImports: Record<string, unknown> = {
  platform: platformSpec,
  wallbid: wallbidSpec,
}
bash
# 4. Generate route files
cd docs-gen && npm run create-spec-routes

Generating operationIds

If the vendor spec lacks operationId fields:

python
import json
spec = json.load(open('my-api.json'))
for path, methods in spec['paths'].items():
    for method, op in methods.items():
        if isinstance(op, dict) and 'operationId' not in op:
            slug = path.replace('/', '_').replace('{', '').replace('}', '')
            op['operationId'] = f"{method}{slug}"
json.dump(spec, open('my-api.json', 'w'), indent=2)

Known limitations

  • 500MB build outputopenapi.json (2MB) explodes into 373MB of pre-rendered HTML. CF Pages handles this fine (20k file limit, no total size limit).
  • 8GB memory for build — configured in the build script. CF Pages build machines handle it.
  • Static imports in config.mts — Vite can't dynamically import JSON at build time. One import line per spec.
  • Requires GitHub/GitLab for native CF Pages integration — migration from Bitbucket in progress. Wrangler direct upload works in the meantime.
  • 500 builds/month on CF free tier — across all projects. ~10/month per client with 50 clients.