agentleFS
cnpmcnpm/cnpmcore/CLAUDE.md

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
  1. Distinctive:

    When working on complex tasks, use extended thinking for deeper analysis:

    line 9
  2. Very specific:

    All controllers extend AbstractController → MiddlewareController

    line 125
  3. Very specific:

    Use EntityUtil.defaultData(data, 'idField') for ID/timestamp defaults

    line 156
  4. Distinctive:

    Always validate requests in this exact order:

    line 215
  5. Very specific:

    Parameter Validation: - Use @eggjs/typebox-validate for type-safe validation

    line 217
  6. Distinctive:

    Authorization: - Check resource-level permissions to prevent privilege escalation

    line 219
  7. Distinctive:

    When changing a Model, update all 3 locations:

    line 243
  8. Distinctive:

    No console statements (use logger)

    line 267
  9. Very specific:

    Avoid any types - use proper typing or unknown

    line 273
  10. Very specific:

    Test files use .test.ts suffix

    line 279
  11. Very specific:

    Use @eggjs/mock for mocking

    line 281
  12. Very specific:

    Use assert from node:assert/strict

    line 282
  13. Distinctive:

    Test both success and error cases

    line 283
  14. Very specific:

    Use getFixtures() for read-only access (reading JSON, tarballs, etc.) — safe for parallel execution

    line 304
  15. Very specific:

    Use copyFixtures() when the test will write to the fixture directory (e.g., npm install creating node_modules, writing .npmrc) — each test …

    line 305
  16. Very specific:

    Always call cleanupFixtures() in after() when using copyFixtures() to clean up temp directories

    line 306
  17. Very specific:

    Never write files directly into test/fixtures/ during test execution.

    line 307
  18. Distinctive:

    Feature Development: Follow bottom-up approach (Model → Entity → Repository → Service → Controller)

    line 395
  19. Distinctive:

    Testing: Write tests at appropriate layer, run individual tests for fast iteration

    line 396
  20. Distinctive:

    Validation: Run linter, typecheck, relevant tests before committing

    line 397
  21. Very specific:

    Commit: Use semantic commit messages (feat/fix/docs/test/chore)

    line 398

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

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 typecheck

Testing

# 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 cov

Database 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.sh

Build

# Clean build artifacts
npm run clean

# Development build
npm run tsc

# Production build
npm run tsc:prod

Architecture - 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 publish
  • getPackageEntity(scope, name) - Fetch package with error handling
  • setCDNHeaders(ctx) - Set cache control headers
  • getAllowSync(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 @Attribute property must be declared with declare (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 throw LeoricClassFieldError at runtime. declare erases the field while keeping the decorator metadata.

Model-Entity Bridge (app/repository/util/):

  • ModelConvertor.convertEntityToModel(entity, ModelClass) - Persist entity
  • ModelConvertor.convertModelToEntity(model, EntityClass) - Load entity
  • Uses @EntityProperty() decorator for complex nested property mapping
  • ModelMetadataUtil for 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:

  1. Parameter Validation - Use @eggjs/typebox-validate for type-safe validation
  2. Authentication - Get authorized user with token role verification
  3. 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 entity
  • saveSomething - Create or update entity
  • removeSomething - Delete entity
  • listSomethings - Query multiple entities (plural)

Modifying Database Models

When changing a Model, update all 3 locations:

  1. SQL migrations: sql/mysql/*.sql AND sql/postgresql/*.sql
  2. ORM Model: app/repository/model/*.ts
  3. 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 check runs format + lint + type-check in one pass; vp check --fix auto-fixes. This is the preferred validation loop.
  • Pre-commit: Vite+ git hooks (installed by vp config via the prepare script). .vite-hooks/pre-commit runs vp staged, which applies vp check --fix to 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 any types - use proper typing or unknown
  • ES modules (import/export) throughout
  • Comprehensive type definitions in all files

Testing

  • Test files use .test.ts suffix
  • Tests mirror source structure in test/ directory
  • Use @eggjs/mock for mocking
  • Use assert from node:assert/strict
  • Test both success and error cases

Test Infrastructure:

  • test/.setup.ts - Global beforeEach/afterEach hooks
  • test/TestUtil.ts - Comprehensive test utilities
  • test/fixtures/ - Mock data and responses

Key TestUtil Methods:

  • TestUtil.createUser(options) - Create test user with auth tokens
  • TestUtil.createPackage(options) - Create full package in system
  • TestUtil.getFullPackage(options) - Get mock package JSON
  • TestUtil.truncateDatabase() - Clear all tables between tests
  • TestUtil.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 by copyFixtures()

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 install creating node_modules, writing .npmrc) — each test gets an isolated copy
  • Always call cleanupFixtures() in after() when using copyFixtures() 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 configuration
  • config/database.ts - Database connection settings
  • config/binaries.ts - Binary package mirror configurations
  • .env - Environment-specific variables (copy from .env.example)
  • tsconfig.json - TypeScript settings (target: ES2022, which turns on useDefineForClassFields; see the Model Layer note on declare)

Development Workflow

  1. Setup: Copy .env.example to .env, start Docker services, initialize database
  2. Feature Development: Follow bottom-up approach (Model → Entity → Repository → Service → Controller)
  3. Testing: Write tests at appropriate layer, run individual tests for fast iteration
  4. Validation: Run linter, typecheck, relevant tests before committing
  5. 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

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.