Git Workflow & Automated Testing
Complete guide to branching strategy, commit conventions, automated testing with Vitest and Playwright, and tag-driven releases.
On this page
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 formatfor 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 tomainto trigger npm publish + GitHub Release. - Standardized Network: Always use
127.0.0.1for 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:
- Database safety β
scripts/check/check-test-db-safety.tsblocks unsafeprivate.test.ts, liveprivate.tspointing at test DBs, and matching live/test DB names (user data protection). - Format + lint β
bun run check(oxfmt + oxlint). - Lint-staged β
bun run gate:fast. risk:auditβ security-risk scan (all adapters) + secret misuse + slop +bun audit+ OSV (skipped if only docs changed).- Unit tests β
vitest run(skipped if only docs changed). - SBOM sync β when
bun.lock/package.jsonchanged.
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_TOKENis 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
mainCI, a single persistent failure blocks releases.Actions β Auto Release β Run workflowbypasses the gate (workflow_dispatch), accepting an exactversionor abumpstrategy.
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:4173viaTEST_MODE=true. - Location:
tests/e2e/
π§ Smart Test Orchestrator
- Purpose: Reads
git diffand 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:
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) |
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).
--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=trueproduction 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
- Stage changes:
git add . - Commit:
git commit -m "feat(auth): add OAuth2 support"β pre-commit hook runs (DB safety β format+lint β lint-staged β unit tests). - Push:
bun run git push origin feat/my-featureβ pre-push hook runs (production build βbun test tests/integration/). - 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
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).