Appearance
<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 loginCreate 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_TOKENCLOUDFLARE_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 todocs-upstream-internal - Client tag (
4.99.6.1.wecare) → deploys todocs-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:5173Hot-reloads markdown changes. API reference pages are generated from public/openapi.json on startup.
Building locally
bash
cd docs-gen
npm run buildOutputs 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
| Section | Path | Sidebar key | Source of truth |
|---|---|---|---|
| API Guides | docs/api-guides/ | /api-guides/ | Hand-written markdown |
| Business Flows | docs/flows/ | /flows/ | doc-ba pipeline (agent-assisted) |
| API Reference | docs/operations/ | /operations/ | Generated from public/openapi.json |
| Wiki | docs/guides/ | /guides/ | Hand-written markdown |
| Changelog | docs/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-*.mdClient prefixes mirror upstream but with a C prefix:
| Subdirectory | Client prefix | Upstream equivalent |
|---|---|---|
customer/ | CCF-NN | CF-NN |
admin/ | CAD-NN | AD-NN |
system/ | CSY-NN | SY-NN |
integration/ | CIN-NN | IN-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
specs.ts— registry listing every spec (id, name, file path, URL prefix)config.mts— static imports + sidebar generation per spec viauseSidebar({ spec, linkPrefix })docs/{dir}/[operationId].md— per-pageuseOpenapi()with the spec, renders<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.jsonts
// 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-routesGenerating 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 output —
openapi.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.