-
Notifications
You must be signed in to change notification settings - Fork 1
DEVELOPMENT
This guide covers local development setup for contributing to nself-admin.
- Getting Started
- Project Structure
- Development Workflow
- Code Style Guide
- Testing
- Building
- Common Development Tasks
- Debugging
- Common Issues
- Contributing
- Node.js: 18.x or later
- pnpm: 8.x or later
- Docker: 20.10+ (for testing)
- Git: Any recent version
- Code Editor: VS Code recommended
# Clone the repository
git clone https://github.com/nself-org/admin.git
cd nself-admin
# Install dependencies
pnpm install
# Copy environment file (optional)
cp .env.example .env.local
# Start development server
PORT=3021 pnpm devRecommended extensions:
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"yoavbls.pretty-ts-errors",
"streetsidesoftware.code-spell-checker"
]
}Settings (.vscode/settings.json):
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.tsdk": "node_modules/typescript/lib",
"tailwindCSS.experimental.classRegex": [["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]]
}nself-admin/
├── .claude/ # Project instructions for Claude
│ └── CLAUDE.md # Development guidelines
├── docs/ # Documentation
│ ├── ARCHITECTURE.md # Architecture details
│ ├── API.md # API documentation
│ ├── DEPLOYMENT.md # Deployment guide
│ ├── DEVELOPMENT.md # This file
│ ├── MIGRATION.md # Migration guides
│ └── CHANGELOG.md # Release notes
├── public/ # Static assets
│ ├── sw.js # Service worker
│ └── offline.html # Offline page
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API routes
│ │ ├── login/ # Login page
│ │ ├── build/ # Build wizard
│ │ ├── config/ # Configuration pages
│ │ ├── database/ # Database pages
│ │ ├── services/ # Service management pages
│ │ ├── deployment/ # Deployment pages
│ │ ├── cloud/ # Cloud provider pages
│ │ ├── plugins/ # Plugin pages
│ │ ├── monitor/ # Monitoring pages
│ │ ├── settings/ # Settings pages
│ │ ├── layout.tsx # Root layout
│ │ └── page.tsx # Dashboard
│ ├── components/ # React components
│ │ ├── ui/ # UI primitives
│ │ ├── build/ # Build wizard components
│ │ ├── config/ # Config components
│ │ ├── database/ # Database components
│ │ ├── services/ # Service components
│ │ ├── skeletons/ # Loading skeletons
│ │ ├── Header.tsx # Top navigation
│ │ ├── Layout.tsx # Main layout
│ │ └── Navigation.tsx # Sidebar navigation
│ ├── contexts/ # React contexts
│ │ └── AuthContext.tsx # Auth state
│ ├── hooks/ # Custom hooks
│ │ ├── useDashboardData.ts
│ │ ├── useServiceStatus.ts
│ │ ├── useSession.ts
│ │ └── useWebSocket.ts
│ ├── lib/ # Utilities and libraries
│ │ ├── api-client.ts # API client
│ │ ├── auth-db.ts # Auth database
│ │ ├── database.ts # LokiJS database
│ │ ├── nselfCLI.ts # nself CLI wrapper
│ │ ├── validation.ts # Zod schemas
│ │ └── utils.ts # Utility functions
│ └── services/ # Background services
│ └── SimplifiedPolling.ts # Polling service
├── tests/ # Test files
│ ├── e2e/ # Playwright tests
│ └── accessibility/ # Accessibility tests
├── .env.example # Example environment file
├── .eslintrc.json # ESLint configuration
├── .prettierrc # Prettier configuration
├── jest.config.js # Jest configuration
├── next.config.mjs # Next.js configuration
├── package.json # Dependencies
├── pnpm-lock.yaml # Lock file
├── tailwind.config.ts # Tailwind configuration
└── tsconfig.json # TypeScript configuration
src/app/ - Next.js App Router pages and API routes
- Every folder is a route
-
page.tsx= page component -
route.ts= API endpoint
src/components/ - Reusable React components
-
ui/= Base components (buttons, inputs, etc.) - Feature-specific folders for complex components
src/lib/ - Utilities, helpers, and services
- Business logic, database access, API clients
src/hooks/ - Custom React hooks
- Data fetching, WebSocket, state management
# Start with default port (3021)
pnpm dev
# Start with custom port
PORT=4000 pnpm dev
# Start with turbo mode (faster builds)
pnpm dev --turbo-
Create a branch
git checkout -b feature/my-feature
-
Make your changes
- Edit files
- Add tests
- Update documentation
-
Run quality checks
pnpm run lint # Check for errors pnpm run format # Auto-format code pnpm run type-check # TypeScript validation pnpm test # Run unit tests
-
Test manually
- Start dev server
- Test in browser
- Check console for errors
-
Commit changes
git add . git commit -m "feat: add new feature"
-
Push and create PR
git push origin feature/my-feature # Open PR on GitHub
Next.js supports hot module replacement:
- Changes to components → instant reload
- Changes to API routes → automatic restart
- Changes to config → manual restart needed
Use strict typing:
// Good
interface ServiceStatus {
name: string
status: 'running' | 'stopped' | 'error'
uptime: number
}
function getServiceStatus(name: string): ServiceStatus {
// ...
}
// Avoid
function getServiceStatus(name: any): any {
// ...
}Prefer interfaces over types:
// Good
interface User {
id: string
name: string
}
// OK for unions
type Status = 'active' | 'inactive'Use proper error handling:
// Good
try {
await someOperation()
} catch (error) {
const err = error as Error
console.error('Operation failed:', err.message)
return { success: false, error: err.message }
}
// Avoid
try {
await someOperation()
} catch (error) {
console.error(error) // Unknown type
}Functional components with TypeScript:
// Good
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
}
export function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
>
{label}
</button>
)
}Use proper hooks:
// Good
function MyComponent() {
const [count, setCount] = useState(0)
const [data, setData] = useState<Data | null>(null)
useEffect(() => {
fetchData().then(setData)
}, [])
return <div>{count}</div>
}Standard response format:
// Good
export async function GET(request: Request) {
try {
const data = await fetchData()
return NextResponse.json({ success: true, data })
} catch (error) {
return NextResponse.json(
{
success: false,
error: 'Failed to fetch data',
details: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}Files:
- Components:
PascalCase.tsx(e.g.,ServiceCard.tsx) - Utilities:
camelCase.ts(e.g.,apiClient.ts) - API routes:
route.ts - Pages:
page.tsx
Variables:
- Constants:
UPPER_SNAKE_CASE - Variables:
camelCase - Components:
PascalCase - Types/Interfaces:
PascalCase
Functions:
- Regular functions:
camelCase - React components:
PascalCase - Custom hooks:
useCamelCase
Order:
- External libraries
- Internal utilities
- Components
- Types
- Styles
// Good
import { useState, useEffect } from 'react'
import { NextResponse } from 'next/server'
import { apiClient } from '@/lib/api-client'
import { Button } from '@/components/ui/button'
import type { ServiceStatus } from '@/types'Use path aliases:
// Good
import { Button } from '@/components/ui/button'
import { apiClient } from '@/lib/api-client'
// Avoid
import { Button } from '../../../components/ui/button'Run tests:
pnpm test # Run all tests
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage reportExample test:
// src/lib/__tests__/utils.test.ts
import { formatUptime } from '../utils'
describe('formatUptime', () => {
it('formats seconds correctly', () => {
expect(formatUptime(30)).toBe('30s')
})
it('formats minutes correctly', () => {
expect(formatUptime(90)).toBe('1m 30s')
})
it('formats hours correctly', () => {
expect(formatUptime(3665)).toBe('1h 1m 5s')
})
})Run E2E tests:
pnpm test:e2e # Run all E2E tests
pnpm test:e2e:headed # Run with browser visible
pnpm test:e2e:ui # Interactive mode
pnpm test:e2e:debug # Debug modeExample E2E test:
// tests/e2e/login.spec.ts
import { test, expect } from '@playwright/test'
test('should login successfully', async ({ page }) => {
await page.goto('http://localhost:3021/login')
await page.fill('input[name="password"]', 'testpassword123')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('http://localhost:3021/')
await expect(page.locator('h1')).toContainText('Dashboard')
})Run accessibility audit:
pnpm test:a11yUses Pa11y to check WCAG 2.1 AA compliance.
pnpm buildOutputs to .next/ directory.
NODE_ENV=production pnpm buildOptimizes for production:
- Minification
- Code splitting
- Tree shaking
- Image optimization
# Build image
pnpm docker:build
# Push to registry
pnpm docker:push
# Or use script
./scripts/docker-release.shAnalyze bundle size:
ANALYZE=true pnpm buildOpens interactive bundle analyzer.
-
Create page file:
mkdir -p src/app/my-feature touch src/app/my-feature/page.tsx
-
Create page component:
export default function MyFeaturePage() { return <div>My Feature</div> }
-
Add to navigation:
// src/components/Navigation.tsx { name: 'My Feature', href: '/my-feature', icon: SparklesIcon }
-
Create route file:
mkdir -p src/app/api/my-feature touch src/app/api/my-feature/route.ts
-
Implement handlers:
import { NextResponse } from 'next/server' export async function GET(request: Request) { try { const data = await fetchData() return NextResponse.json({ success: true, data }) } catch (error) { return NextResponse.json({ success: false, error: 'Failed' }, { status: 500 }) } } export async function POST(request: Request) { // ... }
-
Create component file:
touch src/components/MyComponent.tsx
-
Implement component:
interface MyComponentProps { title: string } export function MyComponent({ title }: MyComponentProps) { return <div>{title}</div> }
-
Export from index (if in ui/):
// src/components/ui/index.ts export { MyComponent } from './MyComponent'
-
Create hook file:
touch src/hooks/useMyHook.ts
-
Implement hook:
import { useState, useEffect } from 'react' export function useMyHook(param: string) { const [data, setData] = useState(null) useEffect(() => { // Fetch data }, [param]) return { data } }
LokiJS is used for nAdmin's internal database.
-
Update schema:
// src/lib/database.ts interface MyCollection { key: string value: any timestamp: number }
-
Add collection:
const collection = db.getCollection<MyCollection>('my_collection') || db.addCollection('my_collection', { unique: ['key'], ttl: 3600000, // 1 hour })
React DevTools:
- Install extension
- Inspect component tree
- View props and state
Network Tab:
- Monitor API requests
- Check response times
- Debug failed requests
Console:
- View logs and errors
- Test JavaScript
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3021"
}
]
}Add breakpoints or console.log:
export async function GET(request: Request) {
console.log('Request received:', request.url)
debugger // Pauses execution
// ...
}Enable CLI debug output:
// src/lib/nselfCLI.ts
const result = await execFile('nself', ['status'], {
cwd: projectPath,
env: { ...process.env, DEBUG: '1' },
})
console.log('CLI output:', result.stdout)Error: EADDRINUSE: address already in use :::3021
Solution:
# Find process using port
lsof -i :3021
# Kill process
kill -9 <PID>
# Or use different port
PORT=4000 pnpm devError: Cannot find module '@/components/...'
Solution:
# Reinstall dependencies
rm -rf node_modules pnpm-lock.yaml
pnpm installError: Type errors during development
Solution:
# Run type check
pnpm run type-check
# Restart TypeScript server in VS Code
Cmd+Shift+P → "TypeScript: Restart TS Server"Solution:
# Restart dev server
Ctrl+C
pnpm dev
# Clear Next.js cache
rm -rf .next
pnpm devError: permission denied while trying to connect to the Docker daemon socket
Solution:
# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker psChecklist:
- Code follows style guide
- TypeScript compiles (
pnpm run type-check) - Linting passes (
pnpm run lint) - Code is formatted (
pnpm run format) - Tests pass (
pnpm test) - E2E tests pass (if applicable)
- Documentation updated
- Commit messages follow convention
Format: <type>(<scope>): <subject>
Types:
-
feat- New feature -
fix- Bug fix -
docs- Documentation -
style- Formatting -
refactor- Code restructuring -
test- Adding tests -
chore- Maintenance
Examples:
feat(database): add backup scheduling
fix(auth): resolve session timeout issue
docs(api): update API documentation
refactor(services): simplify service status logic
test(auth): add login flow tests- Submit PR with description
- Automated checks run (CI)
- Reviewer provides feedback
- Make requested changes
- Approved and merged
Questions? Open an issue on GitHub.
Version: 1.0.0 | Updated: 2026-09-14 12:29 UTC | GitHub