docs: add README files and CLI entry point for registry server

- registry/server/README.md: how to start the server (CLI and
  programmatic), environment variables, API reference
- registry/client/README.md: how the client works, programmatic usage
- registry/server/src/bin.ts: CLI entry point (`pnpm-registry` bin)
  configurable via PORT, PNPM_REGISTRY_STORE_DIR,
  PNPM_REGISTRY_CACHE_DIR, and PNPM_REGISTRY_UPSTREAM env vars
This commit is contained in:
Zoltan Kochan
2026-04-08 16:09:03 +02:00
parent 98f8a62fa4
commit 22a8467b87
4 changed files with 164 additions and 0 deletions

43
registry/client/README.md Normal file
View File

@@ -0,0 +1,43 @@
# @pnpm/registry.client
Client library for the pnpm registry server. Reads the local store state, sends it to the server, and writes the received files into the content-addressable store.
## How it works
1. Reads integrity hashes from the local store index (`index.db`).
2. Sends `POST /v1/install` to the pnpm registry server with the project's dependencies and the store integrities.
3. Decodes the binary streaming response — JSON metadata followed by raw file entries.
4. Writes each received file directly to the local CAFS (`files/{hash[:2]}/{hash[2:]}`).
5. Writes store index entries for all new packages in a single SQLite transaction.
6. Returns the resolved lockfile for use with pnpm's headless install (linking phase).
## Usage
This package is used internally by pnpm when the `pnpm-registry` config option is set. It is not intended to be called directly, but can be used programmatically:
```typescript
import { fetchFromPnpmRegistry } from '@pnpm/registry.client'
import { StoreIndex } from '@pnpm/store.index'
const storeIndex = new StoreIndex('/path/to/store')
const { lockfile, stats } = await fetchFromPnpmRegistry({
registryUrl: 'http://localhost:4000',
storeDir: '/path/to/store',
storeIndex,
dependencies: { react: '^19.0.0' },
devDependencies: { typescript: '^5.0.0' },
})
console.log(`Resolved ${stats.totalPackages} packages`)
console.log(`${stats.alreadyInStore} cached, ${stats.filesToDownload} files downloaded`)
// lockfile is ready for headless install
```
## Configuration
Set in `.npmrc` to enable automatically during `pnpm install`:
```ini
pnpm-registry=http://localhost:4000
```

91
registry/server/README.md Normal file
View File

@@ -0,0 +1,91 @@
# @pnpm/registry.server
A pnpm registry server that resolves dependencies server-side and streams only the files missing from the client's content-addressable store.
## How it works
1. Client sends `POST /v1/install` with dependencies, an optional existing lockfile, and the integrity hashes of packages already in its store.
2. Server resolves the full dependency tree using pnpm's own resolution engine.
3. Server computes which file digests the client is missing — at the individual file level, not just the package level.
4. Server streams a binary response: JSON metadata (lockfile + per-package file indexes) followed by the raw content of missing files.
This eliminates sequential metadata round-trips (the server resolves in one shot) and avoids downloading files that already exist in the client's store from other packages.
## Starting the server
### From the command line
```bash
# Build first
pnpm --filter @pnpm/registry.server run compile
# Run with defaults (port 4873, upstream https://registry.npmjs.org/)
node lib/bin.js
# Or configure via environment variables
PORT=4000 \
PNPM_REGISTRY_STORE_DIR=./my-store \
PNPM_REGISTRY_CACHE_DIR=./my-cache \
PNPM_REGISTRY_UPSTREAM=https://registry.npmjs.org/ \
node lib/bin.js
```
### Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `4873` | Port to listen on |
| `PNPM_REGISTRY_STORE_DIR` | `./store` | Directory for the server's content-addressable store |
| `PNPM_REGISTRY_CACHE_DIR` | `./cache` | Directory for package metadata cache |
| `PNPM_REGISTRY_UPSTREAM` | `https://registry.npmjs.org/` | Upstream npm registry to resolve from |
### Programmatic usage
```typescript
import { createRegistryServer } from '@pnpm/registry.server'
const server = await createRegistryServer({
storeDir: '/var/lib/pnpm-registry/store',
cacheDir: '/var/lib/pnpm-registry/cache',
registries: { default: 'https://registry.npmjs.org/' },
})
server.listen(4000, () => {
console.log('pnpm-registry listening on port 4000')
})
```
## Configuring pnpm to use the server
Add to `.npmrc`:
```ini
pnpm-registry=http://localhost:4000
```
Then `pnpm install` will use the registry server for resolution and fetching instead of the normal flow.
## API
### `POST /v1/install`
**Request body** (JSON):
```json
{
"dependencies": { "react": "^19.0.0" },
"devDependencies": { "typescript": "^5.0.0" },
"overrides": {},
"lockfile": null,
"storeIntegrities": ["sha512-abc...", "sha512-def..."]
}
```
**Response** (binary, `Content-Type: application/x-pnpm-install`):
```
[4 bytes: JSON metadata length]
[N bytes: JSON metadata — lockfile, package file indexes, stats]
[file entries: 64B digest + 4B size + 1B mode + content, repeated]
[64 zero bytes: end marker]
```

View File

@@ -17,6 +17,9 @@
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"bin": {
"pnpm-registry": "./lib/bin.js"
},
"exports": {
".": "./lib/index.js"
},

View File

@@ -0,0 +1,27 @@
import { createRegistryServer } from './createRegistryServer.js'
const port = parseInt(process.env['PORT'] ?? '4873', 10)
const storeDir = process.env['PNPM_REGISTRY_STORE_DIR'] ?? './store'
const cacheDir = process.env['PNPM_REGISTRY_CACHE_DIR'] ?? './cache'
const upstream = process.env['PNPM_REGISTRY_UPSTREAM'] ?? 'https://registry.npmjs.org/'
async function main (): Promise<void> {
const server = await createRegistryServer({
storeDir,
cacheDir,
registries: { default: upstream },
port,
})
server.listen(port, () => {
console.log(`pnpm-registry server listening on http://localhost:${port}`)
console.log(` store: ${storeDir}`)
console.log(` cache: ${cacheDir}`)
console.log(` upstream: ${upstream}`)
})
}
main().catch((err) => {
console.error(err)
process.exit(1)
})