This is a comprehensive guide for AI coding agents working on the Condo project - an Open Source property management SaaS platform.
Important Reminder: As an AI coding agent, you are a tool that complements the developer, not a replacement. The developer remains responsible for reviewing, understanding, and validating all code changes you generate. Always provide clear explanations and context for your suggestions.
Condo is a property management platform that allows users to manage tickets, resident contacts, properties, payment tracking, invoices, and a service marketplace. It offers an extension system for mini-apps and is built as a monorepo with multiple apps and shared packages.
- Repository: https://github.com/open-condo-software/condo
- License: MIT
- Architecture: Monorepo with yarn workspaces
- Primary Stack: KeystoneJS, NextJS, ApolloGraphQL, PostgreSQL, Redis
Independent applications that cannot use code from each other. Each app is a standalone service:
condo- Main Keystone + Next web application (property management)pos-integration- POS integration servicebilling-connector- Billing integrationcallcenter- Call center serviceresident-app- Resident mobile/web appdev-portal-web- Developer portal- And 20+ other specialized apps
Internal libraries shared across apps:
@open-condo/ui- UI Kit (recommended for all GUI elements)@open-condo/icons- Icon library@open-condo/*packages - Core utilities and shared functionality@open-condo/webhooks- Webhook functionalityapollo- Apollo client utilitiesbilling- Billing utilities- And more specialized packages
- Node.js: 24.x (LTS) - Use nvm for local development
- Yarn: 3.2.2+ (Berry)
- Python: 3.x (for database migrations)
- PostgreSQL: 16.8 (as defined in docker-compose.yml)
- Redis: 6.2 (as defined in docker-compose.yml)
- Docker & Docker Compose (optional, for databases)
pip install Django==5.2 psycopg2-binary==2.9.10# 1. Start databases (optional, using Docker)
docker compose --profile dbs up -d
# 2. Install Node.js dependencies
yarn install
# 3. Build @open-condo packages (required before running apps)
yarn workspace @app/condo build:deps
# 4. Prepare local environment (creates .env, databases, test users)
node bin/prepare -f condo# Start main condo app in dev mode
yarn workspace @app/condo dev
# Start worker (handles async tasks)
yarn workspace @app/condo worker
# Run tests
yarn workspace @app/condo test
yarn workspace @app/condo test User.test.js # Specific test file
# Build for production
yarn workspace @app/condo build
yarn workspace @app/condo start# Run migrations
yarn workspace @app/condo migrate
# Create new migrations after schema changes
yarn workspace @app/condo makemigrations
# Revert last migration
yarn workspace @app/condo migrate:down
# Unlock migrations table
yarn workspace @app/condo migrate:unlock# Build @open-condo dependencies (required before running apps)
yarn workspace @app/condo build:deps
# Build all packages
yarn build:packages
# Build all apps
yarn build:apps
# Build everything
yarn buildImportant: The project maintains the same version of each package across all apps due to the shared yarn.lock file. This ensures consistency and prevents version conflicts.
# Add package to all apps (recommended for external dependencies)
yarn add <package> -W
# Add package to specific app (use only for app-specific dependencies)
yarn workspace @app/<name> add <package>
# Run command in specific workspace
yarn workspace @app/<name> <command>
# Upgrade packages interactively (updates across all apps)
yarn upgrade-interactive --latestESLint Configuration (.eslintrc.js):
- Indentation: 4 spaces
- Quotes: Single quotes for strings, single quotes for JSX attributes
- Semicolons: Never use semicolons
- Object spacing: Always use spaces inside curly braces:
{ foo } - Trailing commas: Always for multiline arrays/objects/imports
- Space before function paren: Always required
- Import order: Enforced with groups (builtin → external → @open-condo → internal → sibling → parent)
- Newlines between imports: Always required between groups
- Alphabetize imports: Case-insensitive ascending order
Key Rules:
// ✅ Correct
const foo = { bar: 'baz' }
function example () {
return 'hello'
}
// ❌ Wrong
const foo = {bar: 'baz'};
function example() {
return "hello";
}Import Patterns:
// ✅ Use specific lodash imports
import get from 'lodash/get'
// ❌ Don't use named imports from lodash
import { get } from 'lodash'
// ✅ Correct import order
import fs from 'fs' // builtin
import React from 'react' // external
import { getById } from '@open-condo/keystone/schema' // @open-condo
import { MyComponent } from './components' // internalRestricted Imports:
- Don't use
jspdf- usepdfmakeinstead - Don't import from
@open-keystone/fields*or@open-condo/keystone/fields
Stylelint Configuration (.stylelintrc.json):
- Extends
stylelint-config-standardandstylelint-config-rational-order - Supports LESS syntax via
postcss-less - Allows
:globalpseudo-class - Allows
@tailwindat-rules - Allows
fade()function
- TypeScript version: 5.8.3+
- Parser: @typescript-eslint/parser
- Strict mode: Recommended
- JSX: React JSX transform
- No explicit any: Warn level (try to avoid)
Jest Configuration (apps/condo/jest.config.js):
- Test runner: Jasmine2
- Three test projects:
- Schema tests (
*.test.js) - Keystone schema tests - Schema specs (
*.spec.[tj]s) - Unit tests for schema utilities - Main tests - All other tests
- Schema tests (
Test File Patterns:
*.test.js- Schema integration tests (use Keystone test utils)*.spec.jsor*.spec.ts- Unit tests (mock dependencies)
From project testing patterns:
- Simplicity over complexity - Simple tests focusing on basic functionality are more reliable
- Avoid complex mocking - Don't mock everything; test actual behavior when possible
- Focus on interface - Test public API and behavior, not implementation details
- Module structure testing - Verify exports, function signatures, return types
- Consistent mock objects - Use
mockReturnValueinstead ofmockImplementation(() => ({ ... }))for objects - Mock isolation - Each test should set up its own mocks and expectations
- Don't mock condo client - Use real models where possible (follow patterns from existing tests)
- Faker usage - Use
faker.datatype.uuid()for UUID generation (notfaker.string.uuid())
Encapsulation principles:
- Test public APIs, not internal implementation
- Avoid exporting constants/functions solely for testing
- If internal logic is complex enough to need dedicated tests, consider refactoring to a separate module with its own public API
- Integration tests are preferred over exposing internals for unit tests
Complexity vs Coverage trade-off:
- Simple tests are more valuable than comprehensive ones that are hard to maintain
- Focus on testing actual use cases, not every possible edge case
- When mocking becomes too complex, it's a sign to simplify the code or use integration tests
Example:
// ❌ Don't export INTERNAL_CONSTANT just to test it
export const INTERNAL_CONSTANT = 'value'
// ✅ Test the behavior through public API
export function publicFunction() {
const INTERNAL_CONSTANT = 'value'
// ... use it
}Test Modes:
# Real client mode (HTTP requests to remote server)
yarn workspace @app/condo test
# Fake client mode (for debugging, single process)
TESTS_FAKE_CLIENT_MODE=true yarn workspace @app/condo testDebug Database Queries:
DEBUG=knex:query,knex:tx yarn workspace @app/condo devMock Keystone Context:
const mockSudoContext = { /* ... */ }
const mockKeystoneContext = {
sudo: jest.fn().mockReturnValue(mockSudoContext),
}
// Use mockSudoContext in expectations, not mockKeystoneContext.sudo()Mock Class Instances:
// ✅ Use consistent mock objects
const mockClient = { auth: jest.fn() }
MyClass.mockImplementation(() => mockClient)
expect(result).toBe(mockClient)
// ❌ Don't use toBeInstanceOf with mocked classes
expect(result).toBeInstanceOf(MyClass) // Fails with mocks# Lint everything (runs on CI)
yarn lint
# Lint code only
yarn lint:code
yarn lint:code:fix # Auto-fix issues
# Lint styles
yarn lint:styles
# Lint translations
yarn lint:translations
# Lint dependencies
yarn lint:depsRun Semgrep SAST analysis:
# Run analysis (runs on CI)
yarn analysis
# Analyze specific directory
yarn analysis -d apps/condoInstall Semgrep:
brew install semgrep
# or
python3 -m pip install semgrepSemgrep Rules: The project uses multiple security rulesets including p/default, p/security-audit, p/owasp-top-ten, p/secrets, p/sql-injection, and others.
When Semgrep flags code that is intentionally safe but triggers a rule, use // nosemgrep: comments to suppress false positives. Always include a comment explaining why the code is safe.
Format:
// [Explanation why this is safe]
// nosemgrep: rule-id
const result = potentiallyFlaggedCode()Common use cases:
1. Path traversal in template/config paths:
// templatePath is a configured template path - not a user input
// all results of export file generation will be accessible only for authorized end users
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const fileContent = await render(path.resolve(templatePath), replaces)2. HTTP server in tests:
// tests express for a fake gql client
// nosemgrep: problem-based-packs.insecure-transport.js-node.using-http-server.using-http-server
__expressServer = http.createServer(__expressApp).listen(0)3. Disabled TLS in test environment:
// test apollo client with disabled tls
// nosemgrep: problem-based-packs.insecure-transport.js-node.bypass-tls-verification.bypass-tls-verification
const httpsAgentWithUnauthorizedTls = new https.Agent({ rejectUnauthorized: false })4. Dynamic RegExp from controlled input:
// Not a ReDoS case. We generate a specific RE from controlled input
// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
return new RegExp((sol ? '^' : '') + regexString + (eol ? '$' : ''))5. Unsafe format string in logging:
// Safe logging in test environment with controlled input
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.error(`[catchError !!!]${testName}:`, thrownError)Best practices:
- Always explain why the flagged code is safe before the
nosemgrepcomment - Use the full rule ID from Semgrep output
- Only suppress false positives - never ignore real security issues
- Keep explanations concise but clear
- Review suppressions during code review
Finding rule IDs: Run Semgrep to see the rule ID in the output:
yarn analysis
# Output will show: rule-id: javascript.lang.security.audit.path-traversal...Format: <type>(<scope>): <subject>
Types:
feat- New featurefix- Bug fixhotfix- Urgent production fixrefactor- Code refactoringtest- Adding/updating testsdocs- Documentation changesstyle- Code style changesperf- Performance improvementsbuild- Build system changesci- CI configuration changeschore- Other changesrevert- Revert previous commit
Scopes:
global- Affects entire projectdeps- Dependency changes- App names:
condo,pos-integration,billing-connector, etc. - Package names:
webhooks,apollo,billing, etc.
Subject Rules:
- Must start with task number:
DOMA-1234orINFRA-1234(except forhotfixtype) - Must contain at least 2 words after task number
- Max header length: 52 characters
- Max body line length: 72 characters
- Use lowercase (not sentence-case, start-case, pascal-case, or upper-case)
Examples:
feat(condo): DOMA-1234 add payment integration
fix(pos-integration): DOMA-5678 fix receipt generation error
hotfix(global): fix critical security vulnerabilityThe @open-condo/ui package is the recommended way to add GUI elements to pages. It provides a comprehensive set of React UI components designed specifically for the condo ecosystem.
The UI kit is already included in the monorepo. To use it in your app:
// Import components
import { Button, Space, Typography, Modal, Table } from '@open-condo/ui'
import type { ButtonProps, TableProps } from '@open-condo/ui'
// Import styles (in root component)
import '@open-condo/ui/dist/styles.min.css'
// Import icons
import { MoreVertical, Plus, Edit } from '@open-condo/icons'
// Import colors
import { colors } from '@open-condo/ui/colors'
import type { ColorPalette } from '@open-condo/ui/colors'
// Import hooks
import { useBreakpoints, useContainerSize } from '@open-condo/ui/hooks'To discover available components and their APIs:
- Check the main export file:
/packages/ui/src/index.ts- Lists all exported components with their types - Browse component directories:
/packages/ui/src/components/- Each component has its own folder - View Storybook examples:
/packages/ui/src/stories/*.stories.tsx- Interactive examples with usage patterns - Search the codebase: Use grep to find real-world usage:
grep -r "from '@open-condo/ui'" apps/condo/ - Check the package README:
/packages/ui/README.md- Installation and basic usage guide
Common component categories include: Layout, Forms & Inputs, Data Display, Feedback, Typography, Navigation, and Utilities.
Basic Button and Space:
import { Button, Space } from '@open-condo/ui'
import { Plus } from '@open-condo/icons'
<Space size={16}>
<Button type='primary' icon={<Plus />}>
Add New
</Button>
<Button type='secondary'>
Cancel
</Button>
</Space>Typography:
import { Typography, Space } from '@open-condo/ui'
<Space direction='vertical' size={8}>
<Typography.Title level={1}>Page Title</Typography.Title>
<Typography.Text type='secondary'>Description text</Typography.Text>
</Space>Table with Data:
import { Table } from '@open-condo/ui'
import type { TableColumn } from '@open-condo/ui'
const columns: TableColumn[] = [
{ key: 'name', title: 'Name', dataIndex: 'name' },
{ key: 'status', title: 'Status', dataIndex: 'status' },
]
<Table
columns={columns}
dataSource={data}
loading={loading}
pagination={{ pageSize: 20 }}
/>Modal:
import { Modal, Button } from '@open-condo/ui'
const [isOpen, setIsOpen] = useState(false)
<>
<Button onClick={() => setIsOpen(true)}>Open Modal</Button>
<Modal
open={isOpen}
onCancel={() => setIsOpen(false)}
title='Modal Title'
>
Modal content
</Modal>
</>useBreakpoints - Responsive breakpoint detection:
import { useBreakpoints } from '@open-condo/ui/hooks'
const breakpoints = useBreakpoints()
const isMobile = !breakpoints.TABLET_SMALL
const isTablet = breakpoints.TABLET_SMALL && !breakpoints.DESKTOP_SMALL
const isDesktop = breakpoints.DESKTOP_SMALLBreakpoints:
MOBILE_SMALL(0px)MOBILE_LARGE(360px)TABLET_SMALL(480px)TABLET_LARGE(768px)DESKTOP_SMALL(992px)DESKTOP_LARGE(1200px)
useContainerSize - Container dimensions:
import { useContainerSize } from '@open-condo/ui/hooks'
const [{ width, height }, setRef] = useContainerSize()
return <div ref={setRef}>Width: {width}px</div>Access theme colors in JavaScript/TypeScript:
import { colors } from '@open-condo/ui/colors'
import type { ColorPalette } from '@open-condo/ui/colors'
// Use colors in your components
const myStyle = {
backgroundColor: colors.white,
color: colors.gray[7],
borderColor: colors.blue[5],
}
// Common color palettes available:
// - colors.white, colors.black
// - colors.gray[1-10] - Grayscale palette
// - colors.blue[1-10] - Blue palette
// - colors.green[1-10] - Green palette
// - colors.red[1-10] - Red palette
// - colors.orange[1-10] - Orange palette
// - colors.purple[1-10] - Purple paletteReal-world example:
import { colors } from '@open-condo/ui/colors'
// Using colors in styled components
const StyledDiv = styled.div`
background-color: ${colors.gray[1]};
color: ${colors.gray[9]};
&:hover {
background-color: ${colors.blue[1]};
}
`
// Using colors inline
<Typography.Text style={{ color: colors.gray[7] }}>
Secondary text
</Typography.Text>Access colors in LESS/CSS:
// Import LESS variables
@import (reference) "@open-condo/ui/src/tokens/variables.less";
.my-component {
background-color: @condo-global-color-white;
color: @condo-global-color-gray-7;
border: 1px solid @condo-global-color-blue-5;
}Finding available colors:
- Check
/packages/ui/src/colors/index.tsfor color exports - Browse component styles:
/packages/ui/src/components/*/style.lessfor LESS variable usage - Search codebase:
grep -r "colors.*from.*@open-condo/ui" apps/condo/
Import CSS or LESS variables for consistent theming:
// CSS Variables
import '@open-condo/ui/style-vars/css'// LESS Variables
@import (reference) "@open-condo/ui/style-vars/less";Look for real-world usage patterns in the codebase:
- Search for imports:
grep -r "from '@open-condo/ui'" apps/condo/ - Component examples:
/apps/condo/domains/*/components/*.tsx - Page-level usage:
/apps/condo/pages/**/*.tsx - Storybook examples:
/packages/ui/src/stories/*.stories.tsx
- Always use
@open-condo/uicomponents instead of creating custom UI elements - Import icons from
@open-condo/iconsfor consistency - Use
Spacecomponent for layout spacing instead of custom margins - Use
Typographycomponents for all text rendering - Follow existing patterns - search codebase for similar components before implementing
- Import types alongside components for TypeScript support
- Use hooks for responsive design and container sizing
- Use CSS Modules for component styling - Place only minimal styles that cannot be achieved with UI kit components
Recommended approach: Use CSS Modules for custom component styles, but keep styles minimal. Only add styles that cannot be achieved using @open-condo/ui components.
/* MyComponent.module.css */
.container {
/* Only add styles not available in UI kit */
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.custom-element {
/* Minimal custom styling */
transform: rotate(45deg);
}// MyComponent.tsx
import { Space, Typography } from '@open-condo/ui'
import { colors } from '@open-condo/ui/colors'
import styles from './MyComponent.module.css'
export const MyComponent = () => {
return (
<div className={styles.container}>
{/* Use UI kit components for standard elements */}
<Typography.Title level={2}>Title</Typography.Title>
<Space direction='vertical'>
{/* Custom styled element only when necessary */}
<div className={styles.customElement}>
Custom content
</div>
</Space>
</div>
)
}Note: CSS Modules automatically convert kebab-case class names (.custom-element) to camelCase properties (styles.customElement) for convenient access in TypeScript/JavaScript.
Key principles:
- Prefer UI kit components over custom CSS
- Use CSS Modules (
.module.css) for scoped styles - Keep custom styles minimal and focused
- Use UI kit colors, spacing, and typography tokens when possible
- Only create custom styles for unique layout or visual requirements not covered by the UI kit
- Primary: PostgreSQL 16.8 (with replication support)
- Session/Cache: Redis 6.2 or Valkey
- File Storage: S3 (optional)
- Migration Tool: kmigrator (Django-based)
- Backend: KeystoneJS 5 (GraphQL API)
- Frontend: Next.js (React framework)
- UI Components: @open-condo/ui (custom UI kit)
- Icons: @open-condo/icons
- GraphQL Client: Apollo Client
- Task Queue: Bull (Redis-based)
- Testing: Jest with Jasmine2
- Build Tool: Turbo (monorepo orchestration)
- Containerization: Docker
- Each app has its own database
- Apps communicate via GraphQL APIs
- Shared code lives in
/packages - Environment variables in
.envfiles (generated bybin/prepare) - Migrations managed per-app
When implementing features, favor simplicity over handling rare edge cases if the additional complexity creates significant overhead. It's better to have a simple, maintainable solution that covers 95% of use cases than a complex one attempting to cover 100%.
Example scenarios:
- URL parsing utilities don't need to handle protocol-relative URLs unless there's a concrete use case
- Input validation can focus on common cases rather than exotic inputs
- Helper functions should solve the immediate problem, not anticipate every possible future need
When to add complexity:
- Security-critical paths (always handle edge cases)
- User-facing features with clear requirements
- When the edge case is likely to occur in production
- When technical debt would be higher than upfront complexity
Maintain strong module boundaries even when it makes testing slightly harder.
Key principles:
- Don't export internal constants/utilities just for testing purposes
- Test the public API and behavior, not implementation details
- If something needs testing, consider whether it should be public
- Use integration tests for complex internal logic rather than exposing internals
Testing approach:
// ❌ Don't export INTERNAL_CONSTANT just to test it
export const INTERNAL_CONSTANT = 'value'
// ✅ Test the behavior through public API
export function publicFunction() {
const INTERNAL_CONSTANT = 'value'
// ... use it
}The project uses Pino for structured logging with custom serializers and automatic context enrichment.
const { getLogger } = require('@open-condo/keystone/logging')
const logger = getLogger()
// Simple logging - always use object with msg field
logger.info({ msg: 'Operation completed' })
logger.error({ msg: 'Operation failed' })
logger.warn({ msg: 'Warning message' })
// Structured logging with additional fields
logger.info({
msg: 'User action',
entityId: user.id,
entity: 'User',
status: 'success',
})The logging system defines standard fields for consistent log structure. Use core fields for your logging needs. Other fields are auto-filled by the system or available for specific use cases. See /packages/keystone/logging/serializers.js for the complete list.
Core fields:
msg- Main log message (string)data- Large/dynamic data that will be stringified (use for data you want to read but not search by)entityId- ID of entity to search by (use instead ofuserId,organizationId, etc.)entity- Name of entity related toentityId(e.g.,'User','Organization','Ticket')count- Numeric value for aggregation (sum/avg/percentiles)status- HTTP status code, task status, or any status indicatorerr- Error object (automatically serialized)
1. Use structured logging with standard fields:
// ✅ Good - structured with standard fields
logger.info({
msg: 'Message delivered',
entityId: message.id,
entity: 'Message',
status: 'sent',
data: { transport, recipient },
})
// ❌ Bad - unstructured string interpolation
logger.info(`Message ${message.id} delivered via ${transport}`)2. Use data field for large/dynamic content:
// ✅ Good - large data in data field
logger.info({
msg: 'Processing payment',
entityId: payment.id,
entity: 'Payment',
data: { paymentDetails, metadata },
})
// ❌ Bad - large objects as top-level fields
logger.info({
msg: 'Processing payment',
paymentDetails: { /* large object */ },
metadata: { /* large object */ },
})3. Use entityId + entity for searchable identifiers:
// ✅ Good - standard entity fields
logger.info({
msg: 'Ticket updated',
entityId: ticket.id,
entity: 'Ticket',
})
// ❌ Bad - custom ID fields
logger.info({
msg: 'Ticket updated',
ticketId: ticket.id,
})4. Use err field for errors:
// ✅ Good - err field with error object
logger.error({
msg: 'Failed to send notification',
err: error,
entityId: notification.id,
entity: 'Notification',
})
// ❌ Bad - error as string
logger.error({
msg: 'Failed to send notification',
error: error.message,
})5. Use count for metrics:
// ✅ Good - count for aggregation
logger.info({
msg: 'Batch processed',
count: processedItems.length,
status: 'completed',
})6. Don't pass auto-filled fields manually:
// ❌ Bad - these are filled automatically
logger.info({
msg: 'Request processed',
reqId: req.id, // Don't pass - filled automatically
execId: exec.id, // Don't pass - filled automatically
})// Auto-resolves filename relative to project root
const logger = getLogger()
// Named logger (for shared utilities)
const logger = getLogger('my-service')For complete field definitions and serializers, see:
/packages/keystone/logging/serializers.js- All standard fields and their serializers/packages/keystone/logging/getLogger.js- Logger initialization/packages/keystone/logging/index.js- Exports
- Create feature branch
- Make schema changes if needed
- Run
yarn workspace @app/<name> makemigrations - Implement feature
- Write tests (
.spec.jsfor units,.test.jsfor schema) - Run linting:
yarn lint:code:fix - Run tests:
yarn workspace @app/<name> test - Commit with proper format:
feat(scope): DOMA-1234 description
- Use
TESTS_FAKE_CLIENT_MODE=truefor easier debugging - Enable database query logging:
DEBUG=knex:query,knex:tx - Use IDE debugger with breakpoints
- Check logs in worker process for async tasks
- Don't forget to build packages before running apps:
yarn workspace @app/condo build:deps - Run prepare script for new apps:
node bin/prepare -f <app-name> - Migrations must be created after schema changes
- Worker must be running for async tasks (notifications, imports, exports)
- Use correct faker methods -
faker.datatype.uuid()notfaker.string.uuid() - Import order matters - ESLint will enforce proper grouping
- Don't over-engineer solutions - Implement what's needed now, not what might be needed later
- Don't expose internals for testing - Maintain encapsulation even when it makes testing slightly harder
- Don't handle every edge case - Focus on common scenarios unless dealing with security-critical code
- Never commit
.envfiles (use.env.exampleas template) - API keys and secrets go in environment variables
- Semgrep SAST analysis runs on CI
- Database credentials in docker-compose are for local dev only
- Use
scram-sha-256authentication for PostgreSQL
- PostgreSQL configured with
max_connections=2000,shared_buffers=256MB - Use Redis or Valkey for caching and session storage
- Worker handles async tasks to avoid blocking main app
- Turbo caching speeds up builds
- Use
--concurrency=100%for parallel builds
- Main docs:
/docs/develop.md,/docs/contributing.md,/docs/deploy.md - Migration guide:
/docs/migration.md - Database upgrade guide:
/docs/db-upgrade.md - Keystone docs: https://github.com/keystonejs/keystone-5
- Next.js docs: https://nextjs.org
- Apollo docs: https://www.apollographql.com
Application Ports: Ports for apps are automatically assigned during bin/prepare.js execution:
- Keystone apps:
4000 + app_order(e.g., condo is typically4006) - Next.js apps:
3000(default Next.js port) - Other apps:
4000 + app_order - Ports are configurable in each app's
.envfile after assignment
Database & Cache Ports:
- PostgreSQL master:
5432 - PostgreSQL replica:
5433 - Redis:
6379 - Valkey cluster:
7001,7002,7003
# Full setup from scratch
docker compose up -d postgresdb redis
yarn install
yarn workspace @app/condo build:deps
node bin/prepare -f condo
yarn workspace @app/condo dev
# Run tests
yarn workspace @app/condo test
# Create new app
yarn createapp
# Lint and fix
yarn lint:code:fix
# Build everything
yarn buildAfter running node bin/prepare -f condo:
- URL: http://localhost:4006/admin/signin
- Email: Value of
DEFAULT_TEST_ADMIN_IDENTITYinapps/condo/.env - Password: Value of
DEFAULT_TEST_ADMIN_SECRETinapps/condo/.env