SveltyCMS Complete Workflow Guide
Comprehensive guide covering installation, initialization, testing, and CI/CD workflows for SveltyCMS
On this page
This comprehensive guide covers the entire SveltyCMS lifecycle: installation, first-time setup, server restart workflows, testing strategies, and automated CI/CD deployment. The system is designed to be intelligent, avoiding unnecessary recompilation while maintaining UUID consistency for collections.
π This unified guide replaces:
installation.mdx,testing.mdx, andTESTING_GUIDE.md.old
Table of Contents
Part I: Installation & Setup {#toc-part-i}(#prerequisites)
Part II: System Architecture {#toc-part-ii}(#visual-overview)
- Server Restart Flow
- Collection UUID Management
See Compilation Pipeline for detailed architecture.
- Key Components
- State Machine
- Compilation Pipeline
Part III: Testing & CI/CD {#toc-part-iii}(#testing-overview)
Part IV: Troubleshooting & Reference {#toc-part-iv}(#known-issues)
Part I: Installation & Setup {#body-part-i}
Prerequisites
Before installing SveltyCMS, ensure you have the following installed:
Required Software
1. Node.js (>=24)
- Download: https://nodejs.org/
- Check version:
node --version - Why: Runtime environment for JavaScript
2. Package Manager
Choose one of the following:
- Bun (recommended - 3-4x faster runtime)
curl -fsSL https://bun.sh/install | bash - npm (comes with Node.js)
npm --version - Yarn
npm install -g yarn - pnpm
npm install -g pnpm
3. Git
- Download: https://git-scm.com/
- Check version:
git --version - Why: Version control and repository cloning
4. Database
MongoDB (recommended for first install)
- MongoDB Atlas (cloud): https://www.mongodb.com/cloud/atlas
- Local MongoDB: https://www.mongodb.com/try/download/community
- Connection string format:
- Local:
mongodb://localhost:27017/sveltycms - Atlas:
mongodb+srv://username:password@cluster.mongodb.net/dbname
- Local:
MariaDB/MySQL (alternative)
- Download: https://mariadb.org/download/
- Connection format:
host:port(e.g.,localhost:3306)
Quick Installation
The fastest way to get started with SveltyCMS.
Step 1: Clone Repository
git clone https://github.com/SveltyCMS/SveltyCMS.git
cd SveltyCMS
Step 2: Install Dependencies
Choose your package manager:
Bun (fastest)
bun install
bun run dev
npm
npm install
npm run dev
Yarn
yarn install
yarn dev
pnpm
pnpm install
pnpm dev
Step 3: Setup Wizard
The setup wizard launches automatically on first run at http://localhost:5173/setup:
-
Database Configuration
- Choose: MongoDB, MariaDB, or PostgreSQL
- Enter connection details
- Test connection automatically
-
Database Driver Installation
- Automatically installs required drivers
- Detects your package manager
- Handles dependencies
-
System Language Selection
- Choose default language
- Configures internationalization
-
Initial Data Seeding
- Seeds system settings (53 keys)
- Creates default theme
- Sets up base configuration
-
Admin User Creation
- Set admin email
- Create secure password
- Generate session secrets
-
Completion
- Builds and starts development server
- Opens CMS dashboard automatically
- No server restart required! β¨
Manual Installation
For advanced users who need more control over the installation process.
Step 1: Clone and Install
git clone https://github.com/SveltyCMS/SveltyCMS.git
cd SveltyCMS
bun install # or npm/yarn/pnpm
Step 2: Install Database Driver
Install the driver for your chosen database:
# MongoDB
bun add mongoose
# MariaDB/MySQL
bun add mariadb
# PostgreSQL (when available)
bun add pg
Step 3: Create Environment File
Create config/private.ts in the project root:
import { createPrivateConfig } from "../src/utils/config";
export const privateEnv = createPrivateConfig({
// Database Configuration
DB_TYPE: "mongodb",
DB_HOST: "localhost",
DB_PORT: 27017,
DB_NAME: "sveltycms",
DB_USER: "admin",
DB_PASSWORD: "your_secure_password",
// Security (generate with: openssl rand -hex 32)
JWT_SECRET_KEY: "your_generated_secret_here",
ENCRYPTION_KEY: "your_encryption_key_here",
// Multi-Tenancy (optional)
MULTI_TENANT: false,
});
Generate Secure Secrets:
# Using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# Using OpenSSL
openssl rand -base64 32
Step 4: Test Database Connection
Start the development server and test the connection:
bun run dev
Visit http://localhost:5173/setup or use the API directly:
curl -X POST http://localhost:5173/setup?/testDatabase \
-H "Content-Type: application/json" \
-d '{
"type": "mongodb",
"host": "localhost",
"port": 27017,
"name": "sveltycms",
"user": "admin",
"password": "your_password"
}'
Step 5: Seed Initial Data
Run the seed script or use the API:
# Using seed script (recommended for CI/CD)
bun run scripts/seed-cms.js
# Or through the setup wizard at /setup
Step 6: Create Admin User
# Using create-admin script (recommended for CI/CD)
bun run scripts/create-admin.js
# Or through the setup wizard at /setup
Post-Installation Configuration
After completing installation, configure your SveltyCMS instance:
1. Login to Admin Panel
Visit http://localhost:5173/admin (or your custom URL) and login with your admin credentials.
2. Configure System Settings
Navigate to Settings and configure:
- Site Information: Name, description, URL
- Default Language: Choose primary language
- Theme Preferences: Light/dark mode, accent colors
- Email Settings: SMTP configuration for notifications
- Cache Settings: Enable/disable caching, Redis configuration
3. Create Collections
Navigate to Collections to:
- Create content collections (Posts, Pages, etc.)
- Define field schemas
- Set up relationships between collections
- Configure permissions
4. Set Up Users
Navigate to Users to:
- Create additional user accounts
- Assign roles (admin, editor, viewer)
- Configure permissions
- Manage user sessions
5. Install Widgets
Navigate to Widgets to:
- Browse available widgets
- Install additional functionality
- Activate required widgets
- Configure widget settings
Development & Production
Development Server
Start the development server with hot module reload:
# Using Bun (recommended)
bun run dev
# Using npm
npm run dev
# Using Yarn
yarn dev
# Using pnpm
pnpm dev
The server will start at:
- Frontend:
http://localhost:5173 - Admin Panel:
http://localhost:5173/admin - API:
http://localhost:5173/api - GraphQL:
http://localhost:5173/api/graphql
Production Build
Build for production:
# Build
bun run build
# Preview production build
bun run preview
Deployment Options
Vercel
npm install -g vercel
vercel --prod
Netlify
npm install -g netlify-cli
netlify deploy --prod
Docker
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "build"]
docker build -t sveltycms .
docker run -p 3000:3000 --env-file .env sveltycms
Traditional Server (PM2)
npm install -g pm2
npm run build
pm2 start build/index.js --name sveltycms
pm2 save
pm2 startup
Production Environment Variables
Ensure these are set in production:
NODE_ENV=production
SITE_URL=https://your-domain.com
PUBLIC_URL=https://your-domain.com
# Use strong secrets!
JWT_SECRET_KEY=<generate-with-openssl-rand-base64-32>
ENCRYPTION_KEY=<generate-with-openssl-rand-base64-32>
Directory Structure
After installation, your project structure:
SveltyCMS/
βββ build/ # Production build output
βββ config/ # Configuration files
β βββ private.ts # Database credentials (auto-generated)
β βββ roles.ts # Role definitions
β βββ collections/ # Collection schemas (user-created)
βββ compiledCollections/ # Compiled collections (auto-generated)
β βββ Collections/
β βββ Menu/
βββ docs/ # Documentation
βββ scripts/ # Automation scripts
β βββ seed-cms.js # Database seeding script
β βββ create-admin.js # Admin user creation script
βββ src/
β βββ routes/ # SvelteKit routes
β β βββ admin/ # Admin panel
β β βββ api/ # API endpoints
β β βββ setup/ # Setup wizard
β βββ databases/ # Database adapters
β βββ widgets/ # Widget system
β βββ components/ # Svelte components
β βββ stores/ # Svelte stores
β βββ utils/ # Utility functions
βββ static/ # Static assets
βββ tests/ # Test files
β βββ bun/ # Unit tests
β βββ playwright/ # E2E tests
βββ .github/
β βββ workflows/ # GitHub Actions
β βββ playwright.yml # Test workflow
β βββ auto-release.yaml # Release workflow
βββ package.json
βββ svelte.config.js
βββ tailwind.config.ts
βββ playwright.config.ts
βββ bunfig.toml
βββ tsconfig.json
Part II: System Architecture {#body-part-ii}
Visual Overview
Complete System Lifecycle
graph TB
subgraph "First-Time Setup"
A1[Fresh Install] -->|No private.ts| A2[Vite: Setup Mode]
A2 --> A3[Create Blank Config]
A3 --> A4[Open /setup Wizard]
A4 --> A5[User: Enter DB Config]
A5 --> A6[Action: testDatabase]
A6 -->|β Valid| A7[User: Select Language]
A7 --> A8[Action: seedDatabase]
A8 --> A9[Write Credentials]
A8 --> A10[Seed Settings/Themes]
A8 --> A11[Create Collections]
A12[User: Admin Form] --> A13[Action: completeSetup]
A13 --> A14[Create Admin + Session]
A13 --> A15[Init Global System]
A13 --> A16[Content System Init]
A16 --> A17[READY State]
end
subgraph "Server Restart (Phased)"
B1[Server Start] -->|private.ts exists| B2[Vite: Normal Mode]
B2 --> B3[Scan Compiled Collections]
B3 --> B4{For Each Collection}
B4 --> B5{Hash Changed?}
B5 -->|No| B6[Skip - Preserve UUID]
B5 -->|Yes| B7[Recompile - Keep UUID]
B6 --> B8[First Request]
B7 --> B8
B8 --> B9[handleSystemState]
B9 --> B10[Boot Phase: SETUP]
B10 --> B11[Boot Phase: CORE (Auth/Content)]
B11 --> B13[READY State]
B13 --> B12[Boot Phase: FULL (Cache/Themes)]
B12 --> B14[WARMED State]
end
A17 -.->|Restart Server| B1
style A1 fill:#f0f0f0,stroke:#333,stroke-width:3px
style A4 fill:#e0e0e0,stroke:#333,stroke-width:2px
style A8 fill:#e0e0e0,stroke:#333,stroke-width:2px
style A13 fill:#e0e0e0,stroke:#333,stroke-width:2px
style A17 fill:#d0d0d0,stroke:#333,stroke-width:4px
style B1 fill:#f0f0f0,stroke:#333,stroke-width:3px
style B7 fill:#e0e0e0,stroke:#333,stroke-width:2px
style B14 fill:#d0d0d0,stroke:#333,stroke-width:4px
First-Time Setup Architecture
graph TB
A[Vite Startup] -->|No private.ts| B[setupWizardPlugin]
B --> C[Create Blank private.ts]
B --> D[Compile Collections]
B --> E[Open /setup in Browser]
E --> F[User: DB Config]
F --> G[Action: testDatabase]
G -->|Success| H[User: System Language]
H --> I[Action: seedDatabase]
I --> J[Write private.ts]
I --> K[Seed Settings]
I --> L[Seed Themes]
I --> M[Seed Collections]
N --> O[Action: completeSetup]
O --> P[Create Admin User]
O --> Q[Initialize Global System]
O --> R[Initialize Content System]
O --> S[Set Session Cookie]
S --> T[Redirect to CMS]
style A fill:#f0f0f0,stroke:#333,stroke-width:3px
style E fill:#e0e0e0,stroke:#333,stroke-width:2px
style I fill:#e0e0e0,stroke:#333,stroke-width:2px
style O fill:#e0e0e0,stroke:#333,stroke-width:2px
style T fill:#d0d0d0,stroke:#333,stroke-width:4px
Server Restart Architecture
graph TB
A[Vite Startup] -->|private.ts exists| B[cmsWatcherPlugin]
B --> C[Scan Compiled Collections]
C --> D{For Each Collection}
D --> E{Hash Changed?}
E -->|No| F[Skip Compilation]
E -->|Yes| G[Recompile]
F --> H[Preserve UUID]
G --> H
H --> I[First HTTP Request]
I --> J[handleSystemState]
J --> K[Boot Phase: SETUP (Connection)]
K --> L[Boot Phase: CORE (Auth/Settings/Content)]
L --> N[READY State]
N --> M[Boot Phase: FULL (Cache/Themes/Background)]
M --> P[WARMED State]
N --> Q[Content System Auto-Init]
Q --> R[Register Collections]
style A fill:#f0f0f0,stroke:#333,stroke-width:3px
style B fill:#e0e0e0,stroke:#333,stroke-width:2px
style G fill:#e0e0e0,stroke:#333,stroke-width:2px
style H fill:#d0d0d0,stroke:#333,stroke-width:3px
style N fill:#d0d0d0,stroke:#333,stroke-width:4px
First-Time Setup Flow
Phase 1: Vite Startup (Build Time) {#setup-phase1}
File: vite.config.ts
sequenceDiagram
participant Vite
participant FS as Filesystem
participant Browser
Note over Vite: Startup Check
Vite->>FS: Check config/private.ts exists?
FS-->>Vite: β Not found
Note over Vite: Setup Mode Activated
Vite->>FS: Create blank private.ts
Vite->>FS: Create placeholder collections
Vite->>FS: Compile any existing collections
Note over Vite: Open Setup Wizard
Vite->>Browser: Open http://localhost:5173/setup
Note over Browser: User sees setup wizard
Created config/private.ts:
export const privateEnv = createPrivateConfig({
DB_TYPE: "mongodb",
DB_HOST: "", // β Empty values
DB_PORT: 27017,
DB_NAME: "", // β Empty values
DB_USER: "",
DB_PASSWORD: "",
JWT_SECRET_KEY: "", // β Empty
ENCRYPTION_KEY: "", // β Empty
MULTI_TENANT: false,
});
Phase 2: Server Hooks (Runtime)
File: src/hooks.server.ts
π For detailed middleware architecture, see Server Hooks & Middleware
flowchart TB
A[Incoming Request] --> B{handleSystemState}
B -->|System: IDLE| C{handleSetup}
C -->|Check Config| D{private.ts valid?}
D -->|No DB creds| E[isSetupComplete = false]
E --> F{Path check}
F -->|/setup| G[Allow Request]
F -->|Other paths| H[Redirect to /setup]
C -->|Check Database| I{hasAdminUsers?}
I -->|No| E
I -->|Yes| J[Allow Request - Setup Complete]
style E fill:#d0d0d0,stroke:#333,stroke-width:3px
style G fill:#e0e0e0,stroke:#333,stroke-width:2px
style J fill:#e0e0e0,stroke:#333,stroke-width:2px
Phase 3: Setup GUI Interaction
File: src/routes/setup/+page.svelte
stateDiagram-v2
[*] --> database-config: Step 1
database-config --> DatabaseTest: User fills DB form
DatabaseTest --> SystemLanguage: Test passed β
SystemLanguage --> SeedData: Language selected
SeedData --> AdminUser: Database seeded β
AdminUser --> Complete: Admin form submitted
Complete --> [*]: Redirect to CMS
DatabaseTest --> database-config: Test failed β
note right of DatabaseTest
POST /setup?/testDatabase
- Validates connection
- Installs drivers if needed
end note
note right of SeedData
POST /setup?/seedDatabase
- Writes private.ts
- Seeds settings/themes (background)
- Creates collections (background)
- Polling: /api/system/health
end note
note right of Complete
POST /setup?/completeSetup
- Creates admin user
- Initializes system
- Sets session cookie
end note
User completes setup wizard:
Step 1: Database Configuration
β
Step 2: Database Test β /setup?/testDatabase
β
Step 3: System Language Selection
β
Step 4: Seed Data β /setup?/seedDatabase
β
Step 5: Admin User Creation β /setup?/completeSetup
Phase 4: Database Test (Server Function)
Action: src/routes/setup/+page.server.ts (Action: testDatabase)
sequenceDiagram
participant Client as Browser
participant Setup as /setup Actions
participant PM as Package Manager
participant Mongoose
participant DB as MongoDB
Client->>Setup: POST {host, port, name, user, pass}
Note over Setup: Check Driver
Setup->>Mongoose: Try import mongoose
alt Driver Missing
Setup->>PM: Detect package manager (bun/npm/pnpm/yarn)
Setup->>PM: Install mongoose
alt Permission Error
PM-->>Setup: EACCES β
Setup-->>Client: {success: false, manualCommand: "..."}
else Success
PM-->>Setup: Installation complete β
end
end
Note over Setup: Build Connection
Setup->>Setup: buildConnectionString()
Note over Setup: Test Connection
Setup->>DB: Connect with 15s timeout
alt Success
DB-->>Setup: Connected β
Setup->>DB: Authenticate
DB-->>Setup: Auth OK β
Setup->>DB: Get database stats
DB-->>Setup: {collections: 0, dataSize: 0}
Setup-->>Client: {success: true, details: {...}}
else Failure
DB-->>Setup: Connection error
Setup-->>Client: {success: false, error: "..."}
end
POST /setup?/testDatabase
Body: {
type: "mongodb",
host: "localhost",
port: 27017,
name: "sveltycms",
user: "admin",
password: "secret123"
}
Process:
- Detects package manager (bun/npm/pnpm/yarn)
- Installs MongoDB driver if missing (
mongoose) - Builds connection string
- Tests connection with 15s timeout
- Returns detailed validation results
Response:
{
"success": true,
"message": "Connection successful",
"details": {
"authenticated": true,
"dbStats": { "collections": 0, "dataSize": 0 },
"warnings": []
}
}
Phase 5: Seed Database
Endpoint: /setup?/seedDatabase/+server.ts
Called when user clicks βNextβ after successful DB test. This process now runs in the background to prevent UI timeouts and allow real-time progress tracking.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Write config/private.ts β
β - Writes DB credentials from test step β
β - Generates JWT_SECRET_KEY (32 bytes base64) β
β - Generates ENCRYPTION_KEY (32 bytes base64) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Initialize database adapter (setup mode) β
β - getSetupDatabaseAdapter(dbConfig) β
β - Creates MongoDBAdapter β
β - Connects to database β
β - Calls setupAuthModels() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Seed default data (seed.ts) β
β A. seedSettings(adapter) β
β - Creates system_settings collection β
β - Seeds defaultPublicSettings (53 keys) β
β - Seeds defaultPrivateSettings (23 keys) β
β B. seedDefaultTheme(adapter) β
β - Creates system_themes collection β
β - Seeds SveltyCMSTheme β
β C. seedCollectionsForSetup(adapter) β
β - Scans compiledCollections folder β
β - Creates collection models in MongoDB β
β - Reports progress via setupManager β
β - Returns firstCollection info β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Function: seedCollectionsForSetup()
// File: src/routes/setup?/seedDatabase.ts
export async function seedCollectionsForSetup(dbAdapter) {
// 1. Scan filesystem for compiled collections
const { scanCompiledCollections } = await import("@src/content/collectionScanner");
const collections = await scanCompiledCollections();
// 2. Register each collection as a model
for (const schema of collections) {
await dbAdapter.collection.createModel(schema);
// β οΈ Creates MongoDB collection: collection_<uuid>
}
// 3. Return first collection for fast redirect
return { firstCollection: { name: "Posts", path: "/Collections/Posts" } };
}
Database Collections Created:
system_settings β Public/private settings
system_themes β Theme configurations
system_content_structure β Navigation/hierarchy (empty for now)
collection_<uuid_1> β First collection (e.g., Posts)
collection_<uuid_2> β Second collection (e.g., Names)
Phase 6: Complete Setup
Endpoint: /setup?/completeSetup/+server.ts
Called when user submits admin user form.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Parse private.ts from filesystem β
β - Bypasses Vite's import cache β
β - Uses regex to extract config values β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Create setup database adapter β
β - getSetupDatabaseAdapter(dbConfig) β
β - Creates Auth instance β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Create admin user + session (single transaction)β
β - setupAuth.createUserAndSession({ β
β username, email, password, β
β role: 'admin', isRegistered: true β
β }) β
β - Inserts into auth_users collection β
β - Creates session in auth_sessions collection β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Initialize global system (db.ts) β
β - initializeWithFreshConfig() β
β - Reloads private.ts from filesystem β
β - Connects global dbAdapter β
β - Loads settings from database β
β - Initializes ThemeManager β
β - Initializes MediaFolder β
β - State: IDLE β INITIALIZING β READY β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Initialize Content System β
β - contentSystem.initialize(undefined, true) β
β - Loads collections from database β
β - Builds content structure cache β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 6. Invalidate caches β
β - invalidateSettingsCache() β
β - invalidateSetupCache() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 7. Send welcome email (optional, non-fatal) β
β - POST /api/send-mail β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 8. Set session cookie β
β - Creates httpOnly session cookie β
β - Sets theme cookies β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 9. Return redirect path β
β - Uses firstCollection from seed step (fast!) β
β - Redirects to /{lang}/{firstCollection.path} β
β - NO SERVER RESTART REQUIRED! β¨ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Response:
{
"success": true,
"message": "Setup complete! Welcome to SveltyCMS! π",
"redirectPath": "/en/Collections/Posts",
"loggedIn": true,
"requiresHardReload": false,
"requiresServerRestart": false
}
Server Restart Flow
Phase 1: Vite Startup (Build Time) {#phase-1-vite-startup-build-time-2}
File: vite.config.ts
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Vite starts β
β - Checks if config/private.ts exists β
β - isSetupComplete() β true (config exists + β
β has valid DB credentials) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. cmsWatcherPlugin() activates β
β - Initializes collection structure β
β - Compiles collections intelligently β
β - Sets up file watchers β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Phase 2: Intelligent Collection Compilation
File: src/utils/compilation/compile.ts
The compilation system uses content hashing and UUID preservation to avoid unnecessary recompilation:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Scan existing compiled collections β
β - Builds map: path β { jsPath, uuid, hash } β
β - Extracts UUID and hash from JS files β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Scan source collections (config/collections) β
β - Gets list of .ts files β
β - Calculates content hash for each β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Smart recompilation decision β
β For each collection: β
β IF hash matches existing compiled file β
β β SKIP (no changes detected) β
β β PRESERVE existing UUID β
β ELSE β
β β RECOMPILE β
β β PRESERVE existing UUID (if found) β
β β GENERATE new UUID (if new file) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. AST transformation β
β - Converts TypeScript to JavaScript β
β - Adds .js extensions to imports β
β - Injects _id (UUID) into schema β
β - Injects __HASH__ comment β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Cleanup orphaned files β
β - Removes .js files without .ts source β
β - Removes empty directories β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Example Compiled Collection:
// compiledCollections/Collections/Posts.js
// __HASH__:a7f3e9c2d8b1f4a6e3c9d2b8f1a4e6c3
export const schema = {
_id: "550e8400-e29b-41d4-a716-446655440000", // β UUID preserved!
name: "Posts",
icon: "bi:file-text",
fields: [
{ label: "Title", db_fieldName: "title", widget: "text" },
{ label: "Content", db_fieldName: "content", widget: "richtext" },
],
};
Phase 3: Server Initialization
File: src/hooks.server.ts + src/databases/db.ts
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β handleSystemState β
β - System state: IDLE β
β - First request triggers: await dbInitPromise β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β initializeOnRequest() (db.ts) β
β 1. Load private config (privateEnv) β
β 2. Connect to database β
β 3. Load adapters (MongoDBAdapter) β
β 4. Setup auth models β
β 5. Load settings from database β
β 6. Initialize default theme β
β 7. Initialize theme manager β
β 8. Initialize media folder β
β 9. Initialize virtual folders β
β State: IDLE β INITIALIZING β READY β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Content System auto-initializes on first access β
β - Scans compiledCollections β
β - Registers collection models in database β
β - Builds content structure cache β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Collection UUID Management
UUID Assignment Strategy
The Problem: Collections need stable, persistent IDs for:
- Database table names (
collection_<uuid>) - Content references (foreign keys)
- API routes
- Permissions/roles
The Solution: UUIDs are assigned once at compilation and preserved across restarts.
UUID Lifecycle
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. New Collection Created β
β config/collections/Posts.ts β
β export const schema = { name: "Posts", ... } β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. First Compilation β
β - Generates new UUID β
β - Injects into schema._id β
β - Saves to compiledCollections/Collections/ β
β Posts.js β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Collection Modified β
β config/collections/Posts.ts changed β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Smart Recompilation β
β - Detects hash change β
β - PRESERVES existing UUID from compiled file β
β - Recompiles with same UUID β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Server Restart β
β - Reads compiled collections β
β - UUIDs remain stable β
β - Database tables remain consistent β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
UUID Extraction Logic
File: src/utils/compilation/compile.ts
// Extract UUID from compiled JS file
function extractUUIDFromJs(content: string): string | null {
const match = content.match(new RegExp("_id:\\s*" + "[\"']" + "([^\"'\\s]+)" + "[\"']"));
return match ? match[1] : null;
}
// Extract hash from compiled JS file
function extractHashFromJs(content: string): string | null {
const match = content.match(/__HASH__:(\w+)/);
return match ? match[1] : null;
}
AST Transformation:
// Injects UUID into schema during compilation
const schemaUuidTransformer =
(uuid: string): ts.TransformerFactory<ts.SourceFile> =>
(context) =>
(sourceFile) => {
// Finds: export const schema = { ... }
// Injects: _id: "uuid-here"
// Result: export const schema = { _id: "uuid-here", ... }
};
Key Components
1. Configuration Files
| File | Purpose | Created When |
|---|---|---|
config/private.ts |
Database credentials, security keys | Vite startup (empty) β Seed step (populated) |
config/collections/*.ts |
Collection definitions (TypeScript) | User-created |
compiledCollections/**/*.js |
Compiled collections (JavaScript + UUID) | Build time |
2. API Endpoints
| Endpoint | Purpose | Called When |
|---|---|---|
/setup?/testDatabase |
Validates DB connection | Setup wizard (Step 2) |
/setup?/seedDatabase |
Seeds settings, themes, collections | Setup wizard (Step 4) |
/setup?/completeSetup |
Creates admin user, initializes system | Setup wizard (Step 5) |
3. Hooks & Middleware
| Hook | Purpose | Order |
|---|---|---|
handleSystemState |
Blocks requests if system FAILED | 1st |
handleSetup |
Redirects to /setup if incomplete | 2nd |
handleAuthentication |
Validates session, identifies user | 6th |
4. Core Services
| Service | Purpose | File |
|---|---|---|
dbAdapter |
Database abstraction layer | src/databases/db.ts |
contentSystem |
Public system facade | src/content/index.ts |
Auth |
Authentication/authorization | src/databases/auth/index.ts |
SettingsService |
Settings cache management | src/services/settingsService.ts |
State Machine
System States
IDLE β INITIALIZING β READY β DEGRADED β FAILED
β β
ββββββββββββββββββββββββ
(reinit)
| State | Description | Allowed Routes |
|---|---|---|
| IDLE | No valid config or not initialized | /setup, /api/system/health, /login, /static, /.well-known |
| INITIALIZING | Loading config, connecting to DB | /api/system/health |
| READY | Fully operational | All routes |
| DEGRADED | Some services failed, but operational | All routes (with warnings) |
| FAILED | Critical failure, unusable | None (503 errors) |
Transition Triggers
// File: src/stores/system.ts
export function transitionTo(newState: SystemState, reason?: string) {
const oldState = state.overallState;
state.overallState = newState;
state.performanceMetrics.stateTransitions.push({
from: oldState,
to: newState,
timestamp: Date.now(),
reason,
});
}
Known Issues
Issue #1: Collection UUID Tables
Problem:
During seedCollectionsForSetup(), only one collection_<uuid> table gets created in MongoDB instead of one per collection.
Expected Behavior:
collection_550e8400-e29b-41d4-a716-446655440000 (Posts)
collection_7c9e6679-7425-40de-944b-e07fc1f90ae7 (Names)
collection_a8098c1a-f86e-11da-bd1a-00112444be1e (WidgetTest)
Actual Behavior:
collection_550e8400-e29b-41d4-a716-446655440000 (Only one table)
Root Cause (Suspected):
- Race Condition: Multiple
createModel()calls execute in parallel without proper locking - Mongoose Model Registry: Mongoose may be caching models by name, causing conflicts
- MongoDB Collection Creation: MongoDB creates collections lazily on first insert, but the model registration may be conflicting
Location:
// File: src/routes/setup?/seedDatabase.ts
for (const schema of collections) {
await dbAdapter.collection.createModel(schema);
// β οΈ Potential race condition here
}
Suggested Fix:
// Add mutex or serial execution
const results = [];
for (const schema of collections) {
const result = await dbAdapter.collection.createModel(schema);
results.push(result);
// Add small delay to avoid race conditions
await new Promise((resolve) => setTimeout(resolve, 100));
}
Alternative Fix (Better):
// Use batch operation with proper transaction
await dbAdapter.collection.createModels(collections);
// Implement atomic batch creation in MongoCollectionMethods
Issue #2: Vite Cache Invalidation
Problem:
After writePrivateConfig() creates/updates config/private.ts, Viteβs import cache may serve the old version.
Current Workaround:
/setup?/completeSetupreads private.ts directly from filesystem usingfs.readFile()- Bypasses Viteβs import cache with regex parsing
initializeWithFreshConfig()forces fresh import with dynamic import
Better Solution:
- Implement proper Vite HMR invalidation for config files
- Use Viteβs
server.moduleGraph.invalidateModule()API
Flow Diagrams
First-Time Setup (Complete Flow)
sequenceDiagram
participant V as Vite
participant H as Hooks
participant U as User (Browser)
participant API as Setup API
participant DB as Database
participant CS as Content System
V->>V: Check private.ts (missing)
V->>V: Create blank private.ts
V->>V: Compile collections
V->>U: Open /setup in browser
U->>**API Call**: `POST /setup?/testDatabase` (SvelteKit Server Action)
API->>DB: Test connection
DB-->>API: Success
API-->>U: Connection valid
U->>API: POST /setup?/seedDatabase
API->>API: Write private.ts (with credentials)
API->>DB: Seed settings
API->>DB: Seed themes
API->>DB: Seed collections (create models)
API-->>U: Seed complete + firstCollection
U->>API: POST /setup?/completeSetup
API->>DB: Create admin user + session
API->>API: initializeWithFreshConfig()
API->>CS: Initialize Content System
API->>API: Invalidate caches
API-->>U: Setup complete + redirect + session cookie
U->>H: GET /en/Collections/Posts
H->>H: handleSetup: isSetupComplete() β true
H->>CM: Load collection
CM-->>U: Render collection view
Server Restart (Smart Compilation)
sequenceDiagram
participant V as Vite
participant FS as Filesystem
participant CM as Compilation
participant DB as Database
V->>V: Check private.ts (exists + valid)
V->>CM: Trigger intelligent compilation
CM->>FS: Scan compiledCollections/
FS-->>CM: {path β {jsPath, uuid, hash}}
CM->>FS: Scan config/collections/
FS-->>CM: List of .ts files
loop For each collection
CM->>CM: Calculate content hash
alt Hash matches
CM->>CM: SKIP (no changes)
else Hash differs
CM->>CM: RECOMPILE
CM->>CM: PRESERVE UUID
CM->>FS: Write updated .js file
end
end
CM->>V: Compilation complete
V->>DB: Initialize database connection
V->>CS: Initialize Content System
CM->>DB: Register collection models
Best Practices
For Developers
-
Never manually edit compiled collections (
compiledCollections/)- Always edit source files (
config/collections/) - Let the compilation system handle UUID preservation
- Always edit source files (
-
Donβt delete compiled collections during development
- UUIDs would be regenerated, breaking database references
- Use
git cleancarefully
-
Use content hashing for change detection
- The system automatically detects changes via SHA-256 hash
- No manual intervention needed
For System Administrators
-
Backup compiled collections before migrations
tar -czf compiledCollections.backup.tar.gz compiledCollections/ -
Database migrations preserve UUIDs
- Collection IDs are stored in
_idfield - Never modify
_idin database or compiled files
- Collection IDs are stored in
-
Setup completion is atomic
- If setup fails, system reverts to setup mode
- No partial state left behind
Part III: Testing & CI/CD {#body-part-iii}
Testing Overview
SveltyCMS uses a comprehensive testing strategy to ensure code quality across all environments.
Testing Layers
- β E2E Tests (Playwright) - Full user workflows, browser automation
- β Integration Tests - API endpoints, database operations
- β Unit Tests (Bun) - Components, utilities, functions
- β CI/CD Automation - GitHub Actions for testing and releases
Test Coverage
- β Authentication flows (Email, OAuth, 2FA)
- β User management (CRUD, permissions, roles)
- β Collection management (Builder, CRUD, relationships)
- β All 95+ API endpoints tested
- β UI components and stores
- β Database operations
Unit Testing with Bun
Running Bun Tests
# Run all tests
bun test
# Run specific test
bun test tests/bun/api/auth.test.ts
# Watch mode
bun test --watch
# With coverage
bun test --coverage
Test Structure
tests/bun/
βββ api/ # API endpoint tests
βββ auth/ # Authentication tests
βββ widgets/ # Widget tests
βββ mocks/
β βββ setup.ts # Preload file (mocks SvelteKit)
βββ *.test.ts # Various unit tests
Writing Tests
import { describe, it, expect } from "bun:test";
describe("ContentService", () => {
it("should create content", async () => {
const content = await service.create({
title: "Test Post",
});
expect(content.title).toBe("Test Post");
});
});
E2E Testing with Playwright
Running Playwright Tests
# Run all tests
npx playwright test
# Specific test
npx playwright test signupfirstuser.spec.ts
# Debug mode
npx playwright test --debug
# UI mode
npx playwright test --ui
Test Files
tests/playwright/
βββ signupfirstuser.spec.ts
βββ oauth-signup-firstuser.spec.ts
βββ login.spec.ts
βββ user-crud.spec.ts
βββ collection-builder.spec.ts
βββ permission-change.spec.ts
GitHub Actions CI/CD
Workflow: Playwright Tests
File: .github/workflows/playwright.yml
Runs comprehensive tests on every push and PR:
name: Playwright and Bun Tests
on:
push:
branches: [main, next]
pull_request:
branches: [main]
workflow_call: {} # Can be called by auto-release
jobs:
test:
strategy:
matrix:
mode: [dev, preview] # Test both modes
steps:
# Start MongoDB service container
- uses: supercharge/mongodb-github-action@1.10.0
with:
mongodb-version: latest
mongodb-username: admin
mongodb-password: admin
# Seed database BEFORE starting server
- run: bun run scripts/seed-cms.js
env:
MONGO_HOST: localhost
MONGO_PORT: 27017
MONGO_DB: SveltyCMS
# Create admin user BEFORE starting server
- run: bun run scripts/create-admin.js
env:
ADMIN_USER: admin
ADMIN_EMAIL: admin@example.com
ADMIN_PASS: Admin123!
# Start server (dev or preview)
- run: bunx vite dev --port 5173 &
if: matrix.mode == 'dev'
# Run tests
- run: bunx playwright test
Workflow: Auto-Release
File: .github/workflows/auto-release.yaml
Automatically releases new versions after tests pass:
name: Semantic Release
on:
push:
branches: [main] # Only release from main
jobs:
test:
uses: ./.github/workflows/playwright.yml # Run tests first
release:
needs: [test] # Only release if tests pass
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for semantic-release
- run: bunx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CI/CD Features
β MongoDB Service - Isolated database for testing β Matrix Testing - Tests both dev and preview modes β Automated Setup - Seeds DB + creates admin automatically β Test Gating - Releases only if tests pass β Semantic Versioning - Automatic version bumps based on commits β Artifact Upload - Test reports and videos on failure
Automated Setup Scripts
For CI/CD environments, SveltyCMS provides scripts that replicate the setup wizard.
Script: Seed Database
File: scripts/seed-cms.js
Mimics /setup?/seedDatabase endpoint:
import { writePrivateConfig } from "../src/routes/setup/writePrivateConfig.js";
import { initSystemFromSetup } from "../src/routes/setup/seed.js";
import { getSetupDatabaseAdapter } from "../src/routes/setup/utils.js";
async function main() {
const dbConfig = {
type: "mongodb",
host: process.env.MONGO_HOST || "localhost",
port: parseInt(process.env.MONGO_PORT || "27017"),
name: process.env.MONGO_DB || "SveltyCMS",
user: process.env.MONGO_USER || "",
password: process.env.MONGO_PASS || "",
};
// Write config/private.ts
await writePrivateConfig(dbConfig);
// Connect and seed
const { dbAdapter } = await getSetupDatabaseAdapter(dbConfig);
await initSystemFromSetup(dbAdapter);
await dbAdapter.disconnect();
}
main().catch((err) => {
console.error("β Seeding failed:", err);
process.exit(1);
});
Usage:
# Local
bun run scripts/seed-cms.js
# CI/CD with environment variables
MONGO_HOST=localhost MONGO_PORT=27017 bun run scripts/seed-cms.js
Script: Create Admin User
File: scripts/create-admin.js
Mimics /setup?/completeSetup endpoint:
import { Auth } from "../src/databases/auth/index.js";
import { getSetupDatabaseAdapter } from "../src/routes/setup/utils.js";
async function main() {
const dbConfig = {
/* same as seed-cms.js */
};
const admin = {
username: process.env.ADMIN_USER || "admin",
email: process.env.ADMIN_EMAIL || "admin@example.com",
password: process.env.ADMIN_PASS || "Admin123!",
};
const { dbAdapter } = await getSetupDatabaseAdapter(dbConfig);
const auth = new Auth(dbAdapter);
try {
await auth.createUser({
...admin,
role: "admin",
isActive: true,
});
console.log("β
Admin user created!");
} catch (error) {
if (error.message?.includes("duplicate")) {
console.log("βΉοΈ Admin user already exists");
} else {
throw error;
}
}
await dbAdapter.disconnect();
}
main().catch((err) => {
console.error("β Admin creation failed:", err);
process.exit(1);
});
Usage:
# Local
bun run scripts/create-admin.js
# CI/CD with custom credentials
ADMIN_USER=admin \
ADMIN_EMAIL=admin@example.com \
ADMIN_PASS=SecurePass123! \
bun run scripts/create-admin.js
Script Benefits
β No UI required - Perfect for CI/CD β Idempotent - Safe to run multiple times β Environment-driven - Uses environment variables β Reuses setup logic - Imports from actual setup endpoints β Error handling - Graceful failure with exit codes
Part IV: Troubleshooting & Reference {#body-part-iv}
Troubleshooting
Setup wizard loops infinitely
Cause: isSetupCompleteAsync() returns false due to missing users
Fix:
# Check if admin users exist
mongo sveltycms --eval "db.auth_users.find().pretty()"
# If empty, complete setup wizard again
# or manually create admin user via MongoDB shell
Collections not appearing after setup
Cause: seedCollectionsForSetup() failed silently
Fix:
# Check logs for collection seeding errors
tail -f logs/app.log | grep "seedCollections"
# Manually trigger collection initialization
# Restart server with DEBUG=true
UUID changed after recompilation
Cause: Compiled collection was deleted or corrupted
Fix:
# Restore from backup
tar -xzf compiledCollections.backup.tar.gz
# Or manually restore UUID in MongoDB
mongo sveltycms --eval 'db.system_content_structure.find({name: "Posts"})'
# Copy _id, then update compiled file
Summary
This comprehensive guide covers the complete SveltyCMS lifecycle:
β Installation - Quick and manual installation methods β First-Time Setup - Wizard-based and automated setup flows β Server Restart - Intelligent recompilation and UUID preservation β Collection Management - UUID assignment and lifecycle β Testing - Unit tests (Bun) and E2E tests (Playwright) β CI/CD - Automated testing and semantic releases β Troubleshooting - Common issues and solutions
Key Highlights
π Zero-Restart Setup - System becomes operational without server restart
π UUID Preservation - Collections maintain stable IDs across restarts
β‘ Smart Compilation - Only recompiles changed collections using content hashing
π§ͺ Comprehensive Testing - 95+ API endpoints, full user workflows
π€ Automated CI/CD - GitHub Actions for testing and releases
π Automated Scripts - seed-cms.js and create-admin.js for CI/CD
β οΈ Known Issue: Collection UUID table creation may have race conditions during seedCollectionsForSetup()
The system is production-ready and fully automated for both local development and CI/CD environments.
Related Documentation
Architecture
- Collection Store Data Flow - How data flows through the collection store
- State Management - System state machine details
- Cache System - Caching strategy and TTLs
API References
- Setup Actions - Complete setup API reference
- Collection Management - Collection CRUD operations
- Authentication & Identity - User authentication and security
Database
- Core Infrastructure - Database adapter system
- MongoDB Methods - MongoDB-specific implementation
Guides
- Complete Workflow Guide - This document (installation, setup, testing, CI/CD)
- Troubleshooting - Common issues and solutions
Quick Reference
Key Files
| File | Purpose | When Modified |
|---|---|---|
vite.config.ts |
Build-time setup detection | Rarely |
src/hooks.server.ts |
Runtime request gatekeeper | When adding middleware |
src/databases/db.ts |
Database initialization | When adding adapters |
src/content/index.ts |
Collection management | When changing collection logic |
src/utils/compilation/compile.ts |
Smart compilation | When changing collection format |
Key Concepts
- Setup Mode: System state when no admin users exist
- UUID Preservation: Collections keep the same ID across restarts
- Smart Compilation: Only recompiles changed collections using content hashing
- Zero-Restart Setup: System becomes operational without server restart
- State Machine: IDLE β INITIALIZING β READY β DEGRADED β FAILED
Performance Benchmarks
| Operation | Expected Time | Notes |
|---|---|---|
| Vite Startup | 2-5 seconds | Includes TypeScript compilation |
| Database Test | 1-3 seconds | May include driver installation |
| Seed Database | 3-8 seconds | Depends on collection count |
| Complete Setup | 4-6 seconds | Includes full system init |
| Total Setup | 10-25 seconds | Excluding user input |
| Collection Compile | 50-200ms per file | With UUID preservation |
| Server Restart | 3-5 seconds | To READY state |