Skip to content

Getting Started ​

This guide covers everything a platform engineer needs to clone, build, run, and deploy the tutorials-ims platform locally. If you only want to author or update tutorial content, see the author guides instead.

Prerequisites ​

  • Node.js 20+ (build scripts use native fetch)
  • npm 10+
  • cf CLI (Cloud Foundry) — required for hybrid dev and deploys
  • mbt (Multi-Target Application Build Tool) — required for local deploys
  • Docker (optional) — for running MailHog or other local SMTP during email testing

Install and run locally ​

bash
npm install
npm run fetch-tutorials   # Fetch tutorial markdown from GitHub + CAP catalog
cds watch                 # Start CAP server (http://localhost:4004)
npm run dev               # Hugo dev server (separate terminal)
npm run build:all         # Full production build

Tutorials must be fetched before dev or build — fetch-tutorials populates .tutorial-cache/ (gitignored) and generates pages into hugo/content/tutorials/ (also gitignored). Delete .tutorial-cache/ to force a full re-fetch from GitHub.

Folder map ​

The full annotated folder map lives in the project README. The most important entry points for local development:

  • approuter/ — Express-based AppRouter; serves Hugo static build, proxies to CAP, mounts admin/scanner/analytics SPAs
  • srv/ — CAP Node.js services (DeveloperService, AdminService, AnalyticsService, ContentStore, ChatService, etc.)
  • app/ — Standalone UI apps: admin-shell/, admin/ (Fiori Elements), analytics-explorer/ (Vue), scanner/ (UI5), display-app/ (Vue)
  • hugo/ and hugo-apps/ — Static site (Hugo) and the Vue 3 page-level islands compiled into hugo/static/js/
  • scripts/ — Build and migration scripts (fetch-tutorials.ts, publish-content.ts, migrate-*.js)
  • db/ — CDS data model + audit/change-tracking annotations
  • test/ — Vitest workspaces: unit/, hybrid/, smoke/

Scripts reference ​

The full script tables (Setup / Dev / Build / Test / Content publishing / QA channel / Migration) live in the project README. Run jq '.scripts' package.json for the complete machine-readable list.

Environment variables ​

Deploy-time variables for the MTA modules (CF env, role collections, secrets) are documented in .deploy/DEPLOY.md. The tables below cover variables commonly set during local dev, CI, and migration.

Build pipeline (fetch + publish) ​

VariableRequiredDefaultDescription
GITHUB_TOKENNo—Avoids GitHub API rate limits when fetching tutorial markdown + commit metadata
TUTORIALS_GITHUB_TOKENNo—CI-side alias for GITHUB_TOKEN (used by deploy.yml, rebuild-content*.yml)
CAP_BASE_URLNohttp://localhost:4004CAP srv URL (build pipeline, publish-content, migration scripts)
CAP_QA_BASE_URLNo—QA-channel CAP srv URL for publish-content:qa
CONTENT_API_KEYYes (publish)—Bearer token for POST /content/publish and /content/rollback
CONTENT_API_KEY_QAYes (QA publish)—Bearer token for QA-channel /content/publish
TUTORIAL_SLUGNo—If set, fetch-tutorials busts the cache for that single slug; rebuild-content.yml skips the RepoCatalog upload
INCLUDE_CONTRIBUTION_REPOSNofalseInclude *-Contribution repos in fetch (prod channel only allows on opt-in)
ONLY_CONTRIBUTION_REPOSNofalseQA channel: fetch from *-Contribution repos exclusively

CAP runtime (srv/) ​

VariableRequiredDefaultDescription
CONTENT_API_KEYYes—Required to accept content publish writes; without it /content/publish returns 401
SUBMISSION_SALT_SECRETYes (feedback)—IP-hash salt for /feedback/submit; bridge returns 503 if missing
EXPOSE_CAP_UINofalseEnables /_dev Swagger UI + CAP index page (DEV/QA only — never set in prod)
CHAT_MODEL_NAMENo—Override the Joule chat completion model
SEARCH_RATE_LIMIT_MAXNo60Per-IP search request limit per window
SEARCH_RATE_LIMIT_WINDOW_MSNo60000Search rate-limit window in ms
DASHBOARD_URLNoProduction URLTutorial Dashboard URL injected into notification emails
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_FROM / SMTP_PASSNo—SMTP transport for local email testing (e.g., MailHog). In deployed environments these live in BTP Credential Store, managed via /admin-ui/#secrets-display — see SMTP rotation runbook.

Approuter (approuter/) ​

VariableRequiredDefaultDescription
REBUILD_API_KEYYes (rebuild)—Bearer token for the approuter live-rebuild webhook
CAP_BASE_URLNo (CF: VCAP)—CAP srv URL for proxy fallback when running standalone

Testing ​

VariableRequiredDefaultDescription
SMOKE_BASE_URLYes (smoke)—Approuter URL — npm run test:smoke target
SMOKE_SRV_URLYes (smoke)—CAP srv URL — npm run test:smoke target
SMOKE_QA_BASE_URL / SMOKE_QA_SRV_URL / SMOKE_QA_TOKENYes (QA smoke)—QA-channel smoke-test endpoints + bearer
SMOKE_ADMIN_TOKENNo—Bearer for admin-only smoke checks
SMOKE_TECH_USER / SMOKE_TECH_PASSWORDNo—Basic-auth credentials for tech-user smoke flow
TECH_USERS / TECH_USERS_MAPPINGNo—Backend tech-user auth config consumed by smoke tests
A11Y_BASE_URLYes (a11y)—Target URL for npm run test:a11y
ALLOW_HYBRID_WRITESNofalseHybrid-test write guard — must be true to permit INSERT/UPDATE/DELETE

QA preview rendering (srv-qa/) ​

VariableRequiredDefaultDescription
PREVIEW_SITE_PATHNobundledPath to preview-site Hugo project
PREVIEW_HUGO_BINNohugoHugo binary to invoke for preview renders
PREVIEW_HUGO_ARGS_PREFIXNo—Extra args prepended to every Hugo preview call
PREVIEW_HUGO_TIMEOUT_MSNo—Per-render timeout
PREVIEW_MAX_CONCURRENTNo—Max concurrent preview renders
PREVIEW_QUEUE_TIMEOUT_MSNo—Queue wait timeout before 503
SRV_URL_QANo—QA srv URL passed to preview renderer

Migration (legacy IMS cutover) ​

VariableRequiredDefaultDescription
IMS_BASE_URLYes (migrate)—Legacy Java IMS approuter URL
IMS_AUTH_TOKENYes (migrate)—Bearer token for Java IMS API
IMS_DB_URL / IMS_DB_USERNAME / IMS_DB_PASSWORDYes (HANA migrate)—Direct HANA creds for migrate:hana (IMSDBUSER schema)
IMS_HANA_CREDENTIALS / CAP_HANA_CREDENTIALSNo—Alternate JSON-form HANA credentials for migration
MIGRATION_OUTPUT_DIRNo.migration-data/Where migration export files are written

Hybrid HANA development ​

When you need to develop against real HANA Cloud (e.g., to test BLOB content serving or HDI artifacts), bind a deployed service instance and run CAP and the approuter locally.

Bind to DEV ​

Run npm run bind:setup once after cloning to wire up the hybrid env files. Before each session, cf login and target the DEV space — cds bind --exec resolves credentials from the targeted space.

Run the stack ​

CAP server with content key:

bash
CONTENT_API_KEY=local-dev-key npx cds bind --exec -- npx cds-serve

Approuter (in a separate terminal, listens on port 5000):

bash
cd approuter && node server.js

Publish tutorial HTML to the bound HANA:

bash
CONTENT_API_KEY=local-dev-key npm run publish-content -- --force

Windows note: the KEY=value cmd inline-prefix syntax is bash/zsh only. In PowerShell use $env:CONTENT_API_KEY="local-dev-key"; npx cds bind --exec -- npx cds-serve, in cmd use set CONTENT_API_KEY=local-dev-key && npx cds bind ..., or run the command from Git Bash.

Why content publishing is required ​

Tutorial HTML is served exclusively from HANA BLOBs — there is no static fallback. Without a publish, every /tutorials/* request returns 404. Always pass --force to bypass delta detection; the default delta mode treats publishes as full snapshots and silently drops slugs not in the payload.

Local deploy process ​

When CI is broken or you need a quick iterative deploy, build and push from .deploy/ directly.

Steps ​

bash
cf target -s <dev-space>
cd .deploy
mbt build
cf deploy mta_archives/tutorials-ims_1.0.0.mtar -e ../deploy/dev.mtaext -f

Optional env vars ​

bash
cf set-env tutorials-srv CONTENT_API_KEY "<your-publish-key>"
cf set-env tutorials-srv EXPOSE_CAP_UI "true"
cf restart tutorials-srv

Verify ​

  • /_dev — Swagger UI is reachable (only when EXPOSE_CAP_UI=true)
  • /scanner-ui/ — UI5 scanner app loads
  • /admin-ui/ — admin shell loads with all components

Troubleshooting ​

  • Stuck deploy: list operations with cf mta-ops, then abort with cf deploy -i <op-id> -a abort
  • Admin UI blank: cf ssh tutorials-approuter -c "ls app/static/admin-ui/" to confirm the bundle was deployed
  • Scanner 404: confirm app/static/scanner-ui/ exists in the deployed approuter

DEV database setup (slug population) ​

After a fresh DB deploy or when slugs are missing (missions/groups show numeric IDs instead of text slugs in /build/catalog), run:

bash
# 1. Ensure you're logged into CF DEV space
cf login

# 2. Run the setup script against HANA (deletes autotest junk + assigns slugs)
npx cds bind --exec -- node scripts/setup-dev-data.cjs

# 3. Verify: /build/catalog should return text slugs like "abap-dev-get-started"
curl -s https://tutorial-system-dev-tutorials-srv.cfapps.eu10-005.hana.ondemand.com/build/catalog | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.missions.slice(0,3).map(m=>m.slug))"

The script uses .migration-data/slug-mapping.json (87 missions, 66 groups) extracted from ContentFiles. It assigns slugs sequentially to records that don't already have one — the specific legacyId doesn't matter since content serving only requires the slug to exist.

Flags: --skip-cleanup (skip autotest deletion), --skip-slugs (skip slug assignment), --dry-run (preview only).

Testing ​

  • npm test — fast unit tests on in-memory SQLite
  • npm run test:hybrid — real HANA via cds bind --exec (set ALLOW_HYBRID_WRITES=true to permit writes)
  • npm run test:smoke — HTTP-based, set SMOKE_BASE_URL + SMOKE_SRV_URL

See Testing Guide for the full setup, fixtures, and CI integration.

Common pitfalls ​

  • Tutorials must be fetched before dev/build — Hugo content is generated, not committed. Run npm run fetch-tutorials after a clean clone.
  • hugo/content/tutorials/ is generated — never edit those files; they're overwritten on the next fetch. Edit parsers in scripts/parsers/ instead.
  • publish-content needs --force in production — default delta mode breaks production publishes. The server treats publishes as full snapshots, so a partial payload silently drops slugs not in it.
  • HANA LOB locator expiry — never SELECT a BLOB column alongside metadata in a single CDS QL query on HANA. Use raw SQL via db.run() for BLOB retrieval (see srv/lib/content-store.js).
  • Node 20+ required — build scripts use native fetch with no polyfill.