cnpmcore
CLAUDE.md738 starsChanged 6 days agoMITView on GitHub
When working on complex tasks, use extended thinking for deeper analysis: Example prompts: Use ultrathink for: cnpmcore is a TypeScript-based private NPM registry implementation for enterprise use. It's built on the Egg.js framework using Domain-Driven Design (DDD) architecture principles and supports both MySQL and PostgreSQL databases. The codebase follows strict DDD layering with clear separation of concerns: Controller Layer (app/port/controller/): Key AbstractController methods: Service Layer (app/core/service/): Repository Layer (app/repository/):
- Reads credentials
We split this CLAUDE.md into the rules it gives an agent
21 rules. About 0 are common, 9 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 9
When working on complex tasks, use extended thinking for deeper analysis:
- Very specific: line 125
All controllers extend AbstractController → MiddlewareController
- Very specific: line 156
Use EntityUtil.defaultData(data, 'idField') for ID/timestamp defaults
- Distinctive: line 215
Always validate requests in this exact order:
- Very specific: line 217
Parameter Validation: - Use @eggjs/typebox-validate for type-safe validation
- Distinctive: line 219
Authorization: - Check resource-level permissions to prevent privilege escalation
- Distinctive: line 243
When changing a Model, update all 3 locations:
- Distinctive: line 267
No console statements (use logger)
- Very specific: line 273
Avoid any types - use proper typing or unknown
- Very specific: line 279
Test files use .test.ts suffix
- Very specific: line 281
Use @eggjs/mock for mocking
- Very specific: line 282
Use assert from node:assert/strict
- Distinctive: line 283
Test both success and error cases
- Very specific: line 304
Use getFixtures() for read-only access (reading JSON, tarballs, etc.) — safe for parallel execution
- Very specific: line 305
Use copyFixtures() when the test will write to the fixture directory (e.g., npm install creating node_modules, writing .npmrc) — each test …
- Very specific: line 306
Always call cleanupFixtures() in after() when using copyFixtures() to clean up temp directories
- Very specific: line 307
Never write files directly into test/fixtures/ during test execution.
- Distinctive: line 395
Feature Development: Follow bottom-up approach (Model → Entity → Repository → Service → Controller)
- Distinctive: line 396
Testing: Write tests at appropriate layer, run individual tests for fast iteration
- Distinctive: line 397
Validation: Run linter, typecheck, relevant tests before committing
- Very specific: line 398
Commit: Use semantic commit messages (feat/fix/docs/test/chore)
≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Claude Code Usage
Thinking Modes
When working on complex tasks, use extended thinking for deeper analysis:
- ultrathink / think harder - Maximum depth reasoning for architectural decisions, complex debugging, and thorough code review
- think - Standard extended thinking for moderate complexity tasks
Example prompts:
- "ultrathink about the best way to implement this feature"
- "think harder about potential edge cases in this code"
Use ultrathink for:
- Architectural design decisions
- Complex refactoring across multiple files
- Debugging intricate issues
- Security vulnerability analysis
- Performance optimization strategies
Project Overview
cnpmcore is a TypeScript-based private NPM registry implementation for enterprise use. It's built on the Egg.js framework using Domain-Driven Design (DDD) architecture principles and supports both MySQL and PostgreSQL databases.
Essential Commands
Development
# Start development server (MySQL)
npm run dev
# Start development server (PostgreSQL)
npm run dev:postgresql
# Lint code (Oxlint via Vite+; type-aware/type-check enabled in vite.config.ts)
npm run lint # alias of: vp lint
# Fix linting issues
npm run lint:fix
# Format code / check formatting
npm run fmt # alias of: vp fmt
npm run fmtcheck # alias of: vp fmt --check
# Unified format + lint + type-check in one pass (Vite+); add --fix to auto-fix
vp check
# TypeScript type checking
npm run typecheckTesting
# Run all tests with MySQL (takes 4+ minutes)
npm run test
# Run all tests with PostgreSQL (takes 4+ minutes)
npm run test:postgresql
# Run single test file (faster iteration, ~12 seconds)
npm run test:local test/path/to/file.test.ts
# Generate coverage report
npm run covDatabase Setup
# MySQL setup
docker compose -f docker-compose.yml up -d
CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-mysql.sh
# PostgreSQL setup
docker compose -f docker-compose-postgres.yml up -d
CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-postgresql.shBuild
# Clean build artifacts
npm run clean
# Development build
npm run tsc
# Production build
npm run tsc:prodArchitecture - Domain-Driven Design (DDD)
The codebase follows strict DDD layering with clear separation of concerns:
Controller (app/port/controller/) ← HTTP interface, validation, auth
↓ depends on
Service (app/core/service/) ← Business logic orchestration
↓ depends on
Repository (app/repository/) ← Data access layer
↓ depends on
Model (app/repository/model/) ← ORM/Database mapping
Entity (app/core/entity/) ← Pure domain models (no dependencies)
Common (app/common/) ← Utilities and adapters (all layers)Layer Responsibilities
Controller Layer (app/port/controller/):
- Handle HTTP requests/responses
- Validate inputs using
@eggjs/typebox-validate - Authenticate users and verify authorization
- Delegate business logic to Services
- All controllers extend
AbstractController→MiddlewareController - Auto-applied middlewares:
AlwaysAuth,Tracing,ErrorHandler
Key AbstractController methods:
ensurePublishAccess(ctx, fullname)- Authorization check for package publishgetPackageEntity(scope, name)- Fetch package with error handlingsetCDNHeaders(ctx)- Set cache control headersgetAllowSync(ctx)- Check if sync should be triggered
Service Layer (app/core/service/):
- Implement core business logic
- Orchestrate multiple repositories
- Publish domain events via
EventBus - Manage transactions
- Use
@SingletonProto({ accessLevel: AccessLevel.PUBLIC })scope - Define command interfaces (e.g.,
PublishPackageCmd) for complex operations
Repository Layer (app/repository/):
- CRUD operations on Models
- Data access and persistence
- Query building and optimization
- Methods named:
findX,saveX,removeX,listXs
Entity Layer (app/core/entity/):
- Pure domain models with business behavior
- No infrastructure dependencies
- Factory pattern:
static create(data)method on each entity - Use
EntityUtil.defaultData(data, 'idField')for ID/timestamp defaults - Type guards for union types (e.g.,
isGranularToken()) EasyData<T, Id>type helper for optional create-time fields
Model Layer (app/repository/model/):
- ORM definitions using Leoric
- Database schema mapping
- No business logic
- Every
@Attributeproperty must be declared withdeclare(e.g.declare packageId: string;). The build targets ES2022, so a plain declaration emits a real class field that shadows Leoric's attribute accessor and makes the model throwLeoricClassFieldErrorat runtime.declareerases the field while keeping the decorator metadata.
Model-Entity Bridge (app/repository/util/):
ModelConvertor.convertEntityToModel(entity, ModelClass)- Persist entityModelConvertor.convertModelToEntity(model, EntityClass)- Load entity- Uses
@EntityProperty()decorator for complex nested property mapping ModelMetadataUtilfor reflection-based property discovery
Infrastructure Adapters (app/infra/)
Enterprise customization layer for PaaS integration:
- NFSClientAdapter: File storage (local/S3/OSS)
- QueueAdapter: Message queue integration
- AuthAdapter: Authentication system
- BinaryAdapter: Binary package storage
- SearchAdapter: Elasticsearch integration
- CacheAdapter: Redis caching with distributed locking
Key Decorators & Annotations
HTTP Layer (from @eggjs/tegg):
@HTTPController()- Marks class as HTTP controller@HTTPMethod({ path, method })- Route definition@HTTPBody()/@HTTPQuery()/@HTTPParam()- Request data injection with typebox validation@HTTPContext() ctx: Context- Inject Egg.js context@Middleware(MiddlewareClass)- Apply middleware to controller
Dependency Injection:
@Inject()- Field-level dependency injection@SingletonProto({ accessLevel: AccessLevel.PUBLIC })- Singleton service scope@ContextProto()- Request-scoped service (e.g.,UserRoleManager)
ORM Layer (from leoric):
@Model()- Marks class as ORM model@Attribute(DataTypes.TYPE, options)- Column definition@EntityProperty('nested.path')- Maps model field to entity property
Lifecycle:
@LifecycleInit()- Post-construction initialization hook
Key Development Patterns
Request Validation Trilogy
Always validate requests in this exact order:
- Parameter Validation - Use
@eggjs/typebox-validatefor type-safe validation - Authentication - Get authorized user with token role verification
- Authorization - Check resource-level permissions to prevent privilege escalation
// Example controller method
async someMethod(@HTTPQuery() params: QueryType) {
// 1. Params already validated by @HTTPQuery with typebox
// 2. Authenticate
const user = await this.userRoleManager.requiredAuthorizedUser(this.ctx, 'publish');
// 3. Authorize (if needed)
const { pkg } = await this.ensurePublishAccess(this.ctx, fullname);
// 4. Execute business logic
return await this.service.doSomething(params);
}Repository Method Naming
findSomething- Query single entitysaveSomething- Create or update entityremoveSomething- Delete entitylistSomethings- Query multiple entities (plural)
Modifying Database Models
When changing a Model, update all 3 locations:
- SQL migrations:
sql/mysql/*.sqlANDsql/postgresql/*.sql - ORM Model:
app/repository/model/*.ts - Domain Entity:
app/core/entity/*.ts
Code Style
Linting & Formatting
The project uses Vite+ (the vp CLI) as the unified toolchain. Lint/format config lives in vite.config.ts (lint and fmt blocks) — there are no standalone .oxlintrc.json/.oxfmtrc.json dotfiles (running oxlint/oxfmt directly would NOT pick up project rules; always go through vp).
- Linter: Oxlint via
vp lint(Rust-based, very fast) with type-aware checking - Formatter: Oxfmt via
vp fmt(sole formatter, no Prettier) - Unified check:
vp checkruns format + lint + type-check in one pass;vp check --fixauto-fixes. This is the preferred validation loop. - Pre-commit: Vite+ git hooks (installed by
vp configvia thepreparescript)..vite-hooks/pre-commitrunsvp staged, which appliesvp check --fixto staged files (replaces the old Husky + lint-staged setup).
Style rules:
- Single quotes (
') - 2-space indentation
- 120 character line width
- ES5 trailing commas
- Max 6 function parameters
- No console statements (use logger)
- Automatic import sorting (type imports first, then builtin, external, relative)
TypeScript
- Strict TypeScript enabled
- Avoid
anytypes - use proper typing orunknown - ES modules (
import/export) throughout - Comprehensive type definitions in all files
Testing
- Test files use
.test.tssuffix - Tests mirror source structure in
test/directory - Use
@eggjs/mockfor mocking - Use
assertfromnode:assert/strict - Test both success and error cases
Test Infrastructure:
test/.setup.ts- Global beforeEach/afterEach hookstest/TestUtil.ts- Comprehensive test utilitiestest/fixtures/- Mock data and responses
Key TestUtil Methods:
TestUtil.createUser(options)- Create test user with auth tokensTestUtil.createPackage(options)- Create full package in systemTestUtil.getFullPackage(options)- Get mock package JSONTestUtil.truncateDatabase()- Clear all tables between testsTestUtil.query(sql)- Execute raw SQL (MySQL/PostgreSQL)TestUtil.getFixtures(name)- Get path to shared fixture (read-only access)TestUtil.copyFixtures(name)- Copy fixture to temp directory (when test needs to write)TestUtil.cleanupFixtures()- Remove all temp directories created bycopyFixtures()
Test Fixture Access:
- Use
getFixtures()for read-only access (reading JSON, tarballs, etc.) — safe for parallel execution - Use
copyFixtures()when the test will write to the fixture directory (e.g.,npm installcreatingnode_modules, writing.npmrc) — each test gets an isolated copy - Always call
cleanupFixtures()inafter()when usingcopyFixtures()to clean up temp directories - Never write files directly into
test/fixtures/during test execution; always copy to a temp directory first
Mocking Patterns:
// Config mocking
mock(app.config.cnpmcore, 'propertyName', newValue);
// HTTP mocking
app.mockHttpclient('https://example.com/path', 'GET', {
data: await TestUtil.readFixturesFile('fixture.json'),
persist: false,
});
// Log assertions
app.mockLog();
await someOperation();
app.expectLog(/pattern/);Getting DI Objects in Tests:
const service = await app.getEggObject(PackageManagerService);Test Pattern:
describe('test/path/to/SourceFile.test.ts', () => {
describe('[HTTP_METHOD /api/path] functionName()', () => {
it('should handle expected behavior', async () => {
const res = await app
.httpRequest()
.put('/path')
.set('authorization', user.authorization)
.send(payload)
.expect(201);
assert.equal(res.body.success, true);
});
});
});Scheduled Task Testing:
await app.runSchedule(SyncPackageWorkerPath);Project Structure
app/
├── common/ # Global utilities and adapters
│ ├── adapter/ # External service adapters
│ └── enum/ # Shared enumerations
├── core/ # Business logic layer
│ ├── entity/ # Domain models
│ ├── event/ # Event handlers
│ ├── service/ # Business services
│ └── util/ # Internal utilities
├── port/ # Interface layer
│ ├── controller/ # HTTP controllers
│ ├── middleware/ # Middleware
│ └── schedule/ # Background jobs
├── repository/ # Data access layer
│ └── model/ # ORM models
└── infra/ # Infrastructure adapters
config/ # Configuration files
sql/ # Database migrations
├── mysql/ # MySQL migrations
└── postgresql/ # PostgreSQL migrations
test/ # Test files (mirrors app/ structure)Important Configuration
config/config.default.ts- Main application configurationconfig/database.ts- Database connection settingsconfig/binaries.ts- Binary package mirror configurations.env- Environment-specific variables (copy from.env.example)tsconfig.json- TypeScript settings (target: ES2022, which turns onuseDefineForClassFields; see the Model Layer note ondeclare)
Development Workflow
- Setup: Copy
.env.exampleto.env, start Docker services, initialize database - Feature Development: Follow bottom-up approach (Model → Entity → Repository → Service → Controller)
- Testing: Write tests at appropriate layer, run individual tests for fast iteration
- Validation: Run linter, typecheck, relevant tests before committing
- Commit: Use semantic commit messages (feat/fix/docs/test/chore)
Integration as NPM Package
cnpmcore can be integrated into Egg.js/Tegg applications as an NPM package, allowing enterprises to:
- Customize infrastructure adapters (storage, auth, queue)
- Override default behavior while receiving updates
- Integrate with existing enterprise systems
See INTEGRATE.md for detailed integration guide.
Performance Notes
Typical command execution times:
- Development server startup: ~20 seconds
- TypeScript build: ~6 seconds
- Full test suite: 4-15 minutes
- Single test file: ~12 seconds
- Linting: <1 second
- Database initialization: <2 seconds
Prerequisites
- Node.js:
^22.18.0 || ^24.11.0 || ^26.0.0 - Database: MySQL 5.7+ or PostgreSQL 17+ (SQLite support in progress)
- Cache: Redis 6+
- Optional: Elasticsearch 8.x
Key Services & Controllers
Core components to understand:
- PackageController: Package CRUD operations
- PackageManagerService: Core package management logic
- BinarySyncerService: Binary package synchronization
- ChangesStreamService: NPM registry change stream processing
- UserController: User authentication and profiles
In this repositoryOne other file it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

