Content Management that can withstand the plundering hoards

Build and verify the repository

Run commands from the repository root unless a code block changes directory. You need Go 1.27 or newer (all modules and go.work declare 1.27), Node.js 20 or newer, npm, and network access to download locked dependencies. Python 3.9+ is used by the documentation checker and package examples. Docker is optional for images and the MariaDB integration fixture. A local build does not need a Kubernetes cluster or production credentials.

Go workspace

There are three independent Go modules, wired by root go.work: controller, frontend and CMS. The repository root is not itself a Go module; do not assume go test ./... at the root tests them all. These commands use workspace-aware module patterns and do not change the current directory:

gofmt -l controller frontend cms
go vet ./controller/... ./frontend/... ./cms/...
go test ./controller/... ./frontend/... ./cms/...
go build ./controller/... ./frontend/... ./cms/...

gofmt -l must print nothing; its exit status alone does not signal formatting violations. For a CI-style failing format check in a POSIX shell:

test -z "$(gofmt -l controller frontend cms)"

Tests cover generated resources/schema validation, file serving, authentication, CSRF, storage durability, rendering and publication. Ordinary tests use fixtures and do not apply Kubernetes resources or contact a production IdP. Browser and real-database integration tests are opt-in below. go vet performs static checks; go build compiles; neither replaces go test.

All modules are present in this repository. GOWORK=off is useful only when intentionally testing one module in isolation; it is not required to work around an absent CMS module. Do not run go mod tidy just to test a documentation change: it edits dependency files and is intended for dependency maintenance.

Editor dependencies and bundle

(cd cms/editor && npm ci && npm test && npm audit --audit-level=high && npm run build)

npm ci installs exactly the committed lockfile and recreates node_modules. Unit tests use Node's test runner/jsdom. Audit contacts the registry; a network failure is different from a reported vulnerability. The build writes ignored cms/editor/dist/ static assets. It does not start a CMS or package an image. There is no Node.js runtime in production.

Browser integration

Install a compatible Chrome/Chromium executable, then run:

(cd cms/editor && BROWSER_PATH=/usr/bin/chromium npm run test:browser)

Use your actual executable path. The command builds the editor, sets VIKING_BROWSER_TEST=1 and runs the Go API browser tests. playwright-core does not itself install a browser. Without an override, the harness looks for local Chrome or a Playwright-installed Chromium. Missing prerequisites can skip browser tests: verify the output shows execution, not only a successful process exit.

The harness starts temporary projects and loopback servers. It checks actual rendered previews, media, themes, packages, publication and session recovery under the CMS CSP. Identity is a test fixture; these tests do not exercise a real provider's account/login configuration. See editor internals.

TestAuthoringBrowser seeds 50 Markdown articles and 50 media assets, uses a theme without navigation, and checks page switches, current theme titles, saved source and one shell request per switch/save. It logs observed browser timings without imposing machine-dependent performance thresholds.

TestGalleriesBrowser installs all four gallery ZIPs, inserts their blocks, changes properties and checks save/reload, opaque previews and publication. It exercises both loop boundaries, autoplay/pause, keyboard controls, reduced motion, responsive columns, small/empty collections and a JavaScript-disabled fallback. Repository checkouts use the real example manifests; standalone CMS Docker contexts use small fixture packages for the same runtime contract.

For backend scaling measurements, run from repository root:

go test -run '^$' -bench '^BenchmarkAuthoringSite$' -benchtime=8x -count=1 ./cms/internal/api

This compares page reads, ordinary saves and theme-shell rendering at 1, 50 and 200 Markdown pages with one media asset per page. Temporary directories use the host's temporary filesystem. Results there do not establish latency on a replicated CSI volume: measure that storage separately, including metadata operations, synchronization latency and CMS CPU throttling.

TestBlogBrowser installs the Blog ZIP, converts Markdown and HTML pages, checks creator/time fields, URI validation, field blocks and Blog-only repeaters, then publishes and visits the resolved URL. Its manifest fixture lives under cms/editor/test/fixtures so standalone editor/CMS Docker contexts work; the editor tests compare it with the example package when repository sources exist.

WordPress database integration

From the root, with Docker running and permission to use it:

scripts/test-wordpress-mysql.sh

This pulls mariadb:11, starts a disposable container on a dynamically allocated loopback port, seeds test tables, reduces the fixture user's access to SELECT, and runs the MySQL integration test. It removes the container on exit and mounts no host data. It needs image-download access; it does not connect to your own WordPress database.

Container builds include verification

From repository root, with Docker running:

docker build -f controller/Dockerfile --build-arg VERSION=local-check -t viking-cms-controller:local .
docker build --build-arg VERSION=local-check -t viking-cms-frontend:local frontend
docker build --build-arg VERSION=local-check -t viking-cms-cms:local cms

Frontend/CMS contexts are their module directories. Controller uses repository root context so its tests can compare the actual deploy CRD/RBAC copies with canonical manifests. Root/Dockerfile-specific ignore rules exclude generated assets/dependencies. Every Dockerfile runs format/vet/Go tests inside the same builder that compiles its binary, after a single cached go mod download. Vet, tests and final compilation reuse Go's module and compilation caches. GOFLAGS=-trimpath keeps verification and final compilation flags aligned so library builds can be reused. Test failures prevent image output. A cached verification layer is reused only when its input source/dependencies are unchanged.

The CMS has a Node 20 editor stage using locked npm ci, unit tests, high-severity audit and Vite build. Its Go 1.27 Debian builder receives that bundle/dependencies and Node, installs Chromium, and runs all Go tests with browser integration enabled. No separate CI artifact or prebuilt host dist directory is needed. There is no Docker fallback to the placeholder UI. This builder uses Debian for browser compatibility; frontend/controller builders continue to use Alpine. Toolchains/Chromium/Node do not enter the scratch runtime.

All runtime binaries are static with CGO disabled, user 65532:65532. CMS/controller include CA certificates for outbound TLS. Builds need network access to image registries, module/npm sources, audit and the CMS builder's APT packages. VERSION stamps internal/buildinfo.Version; without stamping it is dev. Multi-architecture publication depends on the inherited template/platform; Go verification executes for the builder's native architecture, then the frontend binary honors TARGETARCH when supplied.

For plugins/themes, scripts/build-examples.sh builds all language toolchains, packages and verifies actual CMS installation/rendering/publication. Its scratch output is an artifact carrier, not a deployed server image. See examples for individual build scripts and registry distribution.

GitLab pipeline

.gitlab-ci.yml extends the TLSPU container build/manifest template for each component; the builds themselves enforce application tests. The former controller/frontend/CMS/editor/browser test jobs and editor artifact handoff are removed. The test stage remains only for independent GitLab security analyzers, not duplicate application verification.

The build-stage example-packages job builds the example plugins and themes, verifies them through the CMS, then uses TLSPU's /templates/generic-repo/publish.yaml to upload the ZIPs to the project's generic registry. It also retains job artifacts for a week. See registry naming and variables.

The tag-only documentation-publish job runs in the publish stage. It composes the Viking authentication, optional Markdown preparation, branch deletion, upload and publication fragments from /templates/viking/template.yaml. VIKING_FILES selects docs, screenshots and contributing. Preparation stages copies with URI-safe filenames, rewrites page/image links and updates the upload file list without making HTTP requests. Upload sends screenshots as normal media assets and creates hidden named image objects; for example, screenshots/Viking-CMS_in_action.png serves its PNG bytes at /screenshots/Viking-CMS_in_action.png. Markdown uses these named URLs instead of asset-ID URLs. Preparation preserves source files, code examples and Markdown format. Links to unselected repository files point to the tagged commit's source browser. Markdown README files publish at their containing directory: docs/README.md becomes /docs/, docs/user/README.md becomes /docs/user/, and screenshots/README.md becomes /screenshots/. Links to these files and directories use the same URLs, retaining queries and section fragments. Staged files remain Markdown.

Preparation validates local destinations and section fragments before deletion. The job lists pages and deletes only URI descendants beneath /screenshots, /docs and /contributing, preserving their landing/index pages and all other draft paths. It then uploads staged content with menu visibility disabled and publishes the whole site's saved drafts. Hidden child pages remain available to dynamic indexes. Uploading the selected README files replaces the /docs, /screenshots and /contributing landing pages and hides them from menus, just like the other uploads. A landing page keeps its content and menu settings when its README is excluded from the selection. Individual descendant deletions are separate requests; an error stops the job before publication but can leave partial draft changes. Saved changes elsewhere are also included in the whole-site publication. The fixed resource group serializes documentation jobs. Configure the Ipseity inputs and VIKING_URL as CI variables; none of their values belong in YAML. The shared template changes must be available in tlspu/ci-utils before running the job. The live CMS needs named image publication (object.publishImage) and the Markdown heading-ID renderer for section links. Deploy the updated CMS before using these templates; building a container does not deploy it automatically.

The shared templates are referenced without ref pins in this checkout, so inherited publishing behaviour lives outside this repository. Enabled security includes SAST, dependency scanning and secret detection. Container scanning is commented out; SBOM/provenance are planned. Inspect actual pipeline results. Local builds/checks do not upload, push, tag or deploy by themselves.

Documentation-only verification

python3 scripts/check-docs.py
git diff --check

The checker validates local Markdown links/anchors and every docs directory's direct-child index. Read documentation conventions before adding or relocating a page. Application/browser tests are not necessary for pure prose/link edits unless code or executable examples are also changed.