Skip to content

Documentation

Git Workflow & Automated Testing

Complete guide to branching strategy, commit conventions, automated testing with Vitest and Playwright, and tag-driven releases.

9/18/2026
14 min read Edit on GitHub

Introduction

SveltyCMS uses a comprehensive Git workflow with automated testing and semantic versioning to ensure code quality and streamline the release process. A mandatory Local Quality Gate is enforced via native Git hooks to ensure that only verified code reaches the repository.

Our CI/CD and Local pipeline uses:

  • Native Git Hooks: Zero-dependency quality gate (.githooks/pre-commit).
  • Vitest (jsdom): Unified unit testing for components, stores, services, and API handlers.
  • Unified Formatting: oxfmt-powered bun run format for lightning-fast cleanup.
  • Vitest Unit Suite: Fast unit testing (1,100+ tests in under 10 seconds for the full suite).
  • Playwright: Comprehensive E2E testing across multiple database backends.
  • Tag-Driven Releases: Push a v* tag to main to trigger npm publish + GitHub Release.
  • Standardized Network: Always use 127.0.0.1 for local consistency.

πŸ›‘οΈ Mandatory Local Quality Gate

Before any commit or push, SveltyCMS runs automated checks via native Git hooks. Activate them once per clone:

git config core.hooksPath .githooks

Git Hook Overview

SveltyCMS uses native Git hooks for local quality gates β€” no external precheck orchestrator script required.

Pre-commit (DB safety β†’ format+lint β†’ lint-staged β†’ risk:audit β†’ unit β†’ SBOM)

.githooks/pre-commit runs:

  1. Database safety β€” scripts/check/check-test-db-safety.ts blocks unsafe private.test.ts, live private.ts pointing at test DBs, and matching live/test DB names (user data protection).
  2. Format + lint β€” bun run check (oxfmt + oxlint).
  3. Lint-staged β€” bun run gate:fast.
  4. risk:audit β€” security-risk scan (all adapters) + secret misuse + slop + bun audit + OSV (skipped if only docs changed).
  5. Unit tests β€” vitest run (skipped if only docs changed).
  6. SBOM sync β€” when bun.lock / package.json changed.
Important

CI-parity unit rule: pre-commit runs the plain unit suite; pre-push intentionally re-runs it under coverage (test:unit:coverage) plus test:unit:components and test:mutation. Unhandled errors and coverage thresholds only surface under the CI reporter, so this is the one deliberate double-run.

Live data rule: automated local runs never use config/private.ts or the user database β€” only private.test.ts + isolated DB names.

Pre-push (~5–8 min) β€” production build + verifiers + tenant + SQLite integration

.githooks/pre-push runs:

Check Command Est.
Production build (4 adapters) COMPILE_ALL_ADAPTERS=true bun run build ~25–120s
Prod backdoor verify scripts/security/verify-prod-build-backdoor.ts --mode=bench ~5–15s
Bundle size gate scripts/check/check-bundle-size.ts ~1s
Quality gate bun run check ~50s
Unit coverage (CI parity) bun run test:unit:coverage ~65–130s
Component interactions bun run test:unit:components ~11s
Secret misuse scan scripts/security/scan-secret-misuse.ts --strict --full ~1–5s
Hardware tiers probe scripts/probe/probe-hardware-tiers.ts ~1s
Tenant isolation lint:tenant -- --full + test:tenant ~2–20s
Deploy backdoor probe scripts/probe/probe-deploy-testing-api.ts ~30s
SQLite integration tests run-integration.ts --no-build --diff ~35–180s
DB parity (conditional) test:parity when adapter/service code changed ~1–5 min
E2E browser gate changed-path specs + smoke.spec.ts, then flake:budget ~70–180s
Mutation (last) bun run test:mutation ~6s

Multi-adapter DB matrix, the full 9-shard E2E matrix and benchmarks remain CI-only. Pre-push runs the SQLite suite plus the path-mapped browser contracts, so a regression outside those paths still waits for CI. Docs-only pushes skip build + integration + browser + mutation.

Release preparation (opt-in). bun run git push --release (or --release=patch|minor|major) arms a post-gate step: once the nine checks pass, scripts/version.ts auto derives the next SemVer version from the Conventional Commits since the highest v* tag, writes it into package.json and the CycloneDX root component in sbom.json (so the two can never drift), commits them as chore(release): bump version to vX.Y.Z, and prints the push that publishes it. The flag is consumed by scripts/git-safe.ts (git never forwards hook arguments), which is why the hook reads SVELTY_RELEASE. The bump runs only behind a full green gate and never inside the triggering push β€” git resolves the pushed refs first β€” so the release commit stays local until you push again; re-running is a no-op within the same release cycle. Manual equivalent: bun run gate --release.

The manifest/SBOM lockstep is asserted read-only by bun run version:verify, which runs inside bun run check (so the pre-push Quality Gate and CI 03-check both fail on drift) and again in the release workflow before anything is tagged.

bun run gate                               # pre-push gate (build + verifiers + CI unit parity + tenant + SQLite + E2E + mutation)
bun run gate --release                     # …then bump + commit the next SemVer version
bun run prepush                            # same as gate
bun run git push --release origin HEAD:next HEAD:main   # gate + release, one command
bun run version:bump --dry-run             # preview the derived version, write nothing
bun run version:verify                      # package.json ↔ sbom.json consistency (read-only)
bun test --timeout 300000 tests/integration/   # Integration only (needs build/)
bun run test:doctor                        # unit + SQLite integration + gate map
bun run risk:audit                         # same scanners as pre-commit step 4

Automated release (CI) β€” the GitHub Release + npm

auto-release.yaml listens for a successful CI run on main (plus push of a v* tag and manual dispatch). On success it tags exactly the version package.json carries and creates the GitHub Release with generated notes. Hardening added for enterprise predictability:

  • Pre-flight bun run version:verify β€” the release refuses to run when the manifest and SBOM disagree.
  • Post-flight verification β€” the job fails loudly if the tag was not pushed or the GitHub Release was not created (a β€œsuccessful” job with no artifacts is the failure mode the guard exists for).
  • npm is best-effort and honest β€” a missing NPM_TOKEN is a visible ::warning::, a token npm rejects is a visible ::error::; neither can swallow the GitHub Release (which is created first).
  • Escape hatch β€” because every release is gated on a fully green main CI, a single persistent failure blocks releases. Actions β†’ Auto Release β†’ Run workflow bypasses the gate (workflow_dispatch), accepting an exact version or a bump strategy.

Branching Strategy

We have two primary, long-lived branches: main and next.

main Branch (Production)

  • Purpose: Production-ready, stable code. This branch represents the latest official release.
  • Protection: Protected branch requiring pull request reviews.
  • Automated Actions:
    • Runs full Playwright + Vitest test suite.
    • Tag-driven releases: pushing a v* tag triggers npm publish + GitHub Release with auto-generated notes from commits.
  • Version source: Git tags, not package.json.

next Branch (Development)

  • Purpose: Development and staging. This is the primary branch for all new features.
  • Automated Actions:
    • βœ… Runs full test suite on every push.
    • βœ… Tests across multiple databases (MongoDB, PostgreSQL, MariaDB).
    • βœ… No automatic releases (tests only).

Commit Message Convention

Our automated release process depends on a strict commit message format. We use the Conventional Commits specification.

Commit Message Model (Conventional Commits + TQA)

We follow an enhanced Conventional Commits specification to maintain a clear Technical Ledger.

Format:

<type>(<scope>): <subject> [TQA-Verified]

Types:

  • tqa - Resilience & Integrity: Adding chaos tests, state-machine audits, or GDPR verification.
  • perf - Throughput & Latency: Memory leak fixes, JIT optimizations, and benchmark improvements.
  • feat - Feature: New functionality.
  • fix - Stability: Bug fixes.
  • refactor - Clean Code: Internal restructuring without behavior changes.

Mandatory Scopes:

  • resilience: For any changes to circuit breakers or failover logic.
  • temporal: For timezone/date normalization changes.
  • concurrency: For locking and atomic transaction changes.
  • ledger: For documentation and benchmark result updates.

Automated Testing Architecture

πŸ§ͺ Vitest Unit Tests

  • Speed: ⚑ Fast feedback on every commit (full suite via pre-commit).
  • Location: tests/unit/
  • Runner: Vitest with jsdom for components, stores, services, and API logic.
  • Command: bun run test:unit Β· focused security: bun run test:security

πŸ”— Contract Tests (Adapter Parity)

  • Purpose: Identical assertions run against all 4 databases (SQLite, MongoDB, PostgreSQL, MariaDB).
  • Location: tests/integration/databases/contract.test.ts
  • Contracts: Adapter, Auth, Permission, Setup Gating, Resilience.
  • Command: bun run test:integration

🎭 Playwright Tests (E2E)

  • Purpose: Browser automation for critical user journeys.
  • Standard: Always point to 127.0.0.1:4173 via TEST_MODE=true.
  • Location: tests/e2e/

🧠 Smart Test Orchestrator

  • Purpose: Reads git diff and selects required test suites. Unknown changes fail closed.
  • Command: bun run test:smart

πŸ” AI Slop Scanner

  • Purpose: Detects unsafe {@html}, legacy Svelte 4 patterns, missing ARIA, RTL violations, dead exports.
  • Command: bun run slop

Unused-export findings are split into two info buckets: dead-code candidates (referenced nowhere in src/scripts/root/tests/docs β€” actionable) and export-surface (test-only helpers + documented public API β€” a tracked inventory, not dead code). A third advisory bucket, duplicate-export-name, flags the same distinctive export name in several modules β€” usually a legitimate re-export bridge or SvelteKit remote/server contract symmetry, occasionally a real collision. All advisory buckets are summarised as counts by default and listed with bun run scripts/security/slop-scanner.ts --surface.


Tiered Local Testing Strategy

SveltyCMS uses a three-tier testing strategy that balances developer velocity with full CI parity. Each tier corresponds to a different Git lifecycle event:

flowchart LR A["git commit"] -->|"~40–60s"| B["Pre-Commit
DB safety β†’ format+lint
β†’ lint-staged β†’ risk:audit β†’ unit"] C["git push"] -->|"~5–8 min"| D["Pre-Push
Build 4 adapters β†’ backdoor/bundle
β†’ check β†’ secrets β†’ tenant β†’ SQLite integ"]

Tier 1: Pre-Commit (.githooks/pre-commit) β€” Fast Feedback

Runs automatically on every git commit. Target: ~40–60 seconds (full unit suite + risk audit; skipped if only docs changed).

Check Tool
Test config safety check-test-db-safety.ts
Format + lint bun run check (oxfmt + oxlint)
Lint-staged bun run gate:fast
Risk audit bun run risk:audit
Unit tests bun run test:unit (Vitest)
SBOM sync audit:sbom (if lock changed)
Note

Gate vs. inner loop: the pre-commit unit run (bun run test:unit) is the gate β€” always exhaustive, intentionally not selective. For iteration, pick the smallest scope that covers the change: bun run test:unit:area tests/unit/<dir>/ (fastest: one area, no changed-graph build) or bun run test:unit:changed (selective: unpushed commits + dirty tree; prints the resolved diff base and affected-test count, warns loudly when nothing is affected, exits 3 when no base resolves, and escalates to the full suite on setup/config changes).

Tier 2: Pre-Push (.githooks/pre-push) β€” Production Build + Integration

Runs automatically on every git push. Catches build and integration failures before they reach CI.

Check Command
Production build (4 adapters) COMPILE_ALL_ADAPTERS=true bun run build
Prod backdoor + bundle gates verify-prod-build-backdoor + check-bundle-size
Quality + secrets + tenant check + secret scan + test:tenant
SQLite integration tests bun test tests/integration/

Multi-adapter DB integration + E2E + benchmarks are CI-only.

Manual equivalent: bun run prepush / bun run gate (via hardened git wrapper).

Caution

--no-verify is blocked by scripts/git-safe.ts. Use bun run git push β€” bypassing requires the system git binary path deliberately.

Tier 3: Full CI Parity β€” CI Pipeline

CI runs the complete matrix automatically on every PR/push to next. Covers what local hooks skip:

  • Whitebox: format + lint + type check + secret scan + deploy backdoor probe + hardware tier probe (real cgroup limits)
  • Build: COMPILE_ALL_ADAPTERS=true production build
  • DB tests: Full multi-adapter matrix (MongoDB, PostgreSQL, MariaDB, SQLite)
  • E2E: Playwright prep + 6 shards (chromium)
  • Benchmarks: Performance regression detection

All local test commands must use config/private.test.ts only β€” they must not rename, overwrite, or connect through the developer’s config/private.ts (live user data). Isolation is env + policy (private-config-policy.ts), not backup/restore of live config.

Prerequisites for local integration tests: Docker Desktop with MongoDB (27017), MariaDB (3306), and PostgreSQL (5432) on 127.0.0.1.


Scripts Audit

All scripts in scripts/ and their role in the testing pipeline:

Core Scripts (Testing Pipeline)

Script Role Called By
test-doctor.ts Gate map + unit + SQLite integration bun run test:doctor (manual)
test-smart.ts Git-diff-aware test selector bun run test:smart (manual)
test-unit-changed.ts Selective Vitest runner (resolves base) bun run test:unit:changed
security-audit.ts Security vulnerability scanner bun run security
run-e2e.ts Unified E2E runner (CI/dev modes) bun run test:e2e
check-test-db-safety.ts Blocks unsafe test config pre-commit
scan-secret-misuse.ts Secret exposure static analysis CI whitebox / manual
verify-prod-build-backdoor.ts Checks /api/testing stripped in deploy CI build / whitebox
verify-benchmark-local.ts Blocks local benchmarks on test DB names Manual
bundle-stats.ts Bundle size analysis bun run build:stats
check-bundle-size.ts CI bundle size gate CI build job
generate-sbom.ts Software bill of materials generation bun run audit:sbom

Utility Scripts

Script Role Command
security-audit.ts Security vulnerability scanner bun run security
generate-sbom.ts SBOM generation bun run audit:sbom
bundle-stats.ts Bundle size analysis bun run build:stats
upgrade.ts Automated core updates bun run upgrade
setup-system.ts System bootstrap Dev setup helper

Best Practices

Commit Workflow

  1. Stage changes: git add .
  2. Commit: git commit -m "feat(auth): add OAuth2 support" β€” pre-commit hook runs (DB safety β†’ format+lint β†’ lint-staged β†’ unit tests).
  3. Push: bun run git push origin feat/my-feature β€” pre-push hook runs (production build β†’ bun test tests/integration/).
  4. Before PR: Ensure CI passes β€” local hooks already guarantee the same gate.

Manual Verification

If you want to run checks outside the hook lifecycle:

# Pre-commit gate manually
bun run precommit

# Pre-push gate manually
bun run gate
# or: bun run prepush

# Unit + SQLite integration + gate map
bun run test:doctor

# Integration tests (SQLite; needs build/)
bun test --timeout 300000 tests/integration/

# Smart Test Orchestrator β€” auto-detects what to test from git diff
bun run test:smart

# AI Slop Scanner β€” detect code smells
bun run slop

Security & Slop Regression Check

For changes touching auth, middleware, or API routes, also run:

bun run test:security
bun run slop
Note

On Windows (PowerShell), always use ; to chain commands. Avoid && as it is not natively supported in PowerShell.


Troubleshooting

β€œPermission Denied” on Git Hook

If you are on Linux/macOS and the hook fails to execute:

chmod +x .githooks/pre-commit .githooks/pre-push

β€œDatabase connection failed” in Tests

Ensure you are using 127.0.0.1 in your config/private.test.ts and that the test database is accessible.

Bypassing Hooks

bun run git commit and bun run git push block --no-verify. Bypassing requires invoking the system Git binary directly β€” a deliberate act, not muscle memory. CI will reject the same failures the hooks catch.

Docker DB Not Reachable During Pre-Push

If the pre-push hook or local CI reports postgresql is not reachable at 127.0.0.1:5432:

docker compose -f tests/docker-compose.yml --profile postgresql up -d
docker compose -f tests/docker-compose.yml --profile mariadb up -d
docker compose -f tests/docker-compose.yml --profile mongodb up -d

Credentials must match src/utils/test-db-credentials.ts (image defaults: postgres/postgres, root/mariadb, mongo no-auth).


Related Documentation

gitworkflowtestingci-cdreleasedevelopment
Was this page helpful?