Publishing Registry
Publish custom registries to the Registry Server to make them available for CLI tools.
Prepare Registry
Before publishing, ensure your registry structure is correct.
Registry Directory Structure
my-registry/
├── registry.json # Required: Registry configuration file
├── templates/ # Optional: Template files
│ ├── src/
│ │ └── index.ts
│ ├── package.json
│ └── tsconfig.json
└── ... # Other filesregistry.json Example
{
"$schema": "https://registry.rackjs.com/schemas/registry-item.json",
"name": "my-tool",
"namespace": "@company",
"version": "1.0.0",
"type": "registry:feature",
"priority": 4,
"description": "My custom tool",
"files": [
{
"target": "src/config.ts",
"path": "./templates/src/config.ts",
"type": "registry:lib"
}
],
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^5.9.2"
}
}Schema Validation
Using the $schema field provides IntelliSense and validation in your editor.
Storage Path Resolution
When a registry uploads, the server places it at <namespace>/<segments>/<version>/ under storage. The <segments> are derived in this order:
Explicit
pathfield inregistry.json— split on/. The last segment must equalname, otherwise the upload is rejected withUPLOAD_FAILED. Seepathfield reference.typefield — whenpathis absent, mapped via:typeSegment registry:runtimeruntimes/registry:frameworkframeworks/registry:buildbuild/registry:featurefeatures/registry:testingtesting/registry:qualityquality/Fallback —
typenot in the table → flat<namespace>/<name>/.
Example: a registry with name: "my-tool", type: "registry:feature", namespace @company is stored at @company/features/my-tool/<version>/. The resolved path is reflected in the success response, the canonical read URL GET /registries/<namespace>/<segments>/<version>, and webhook payloads.
When to set path explicitly
Set path only when the storage location should differ from the type-derived default — for example, classifying a registry:feature under a framework folder ("path": "frameworks/vue/router"). For most registries, type alone is enough.
Package Registry
Package the registry in tar.gz format.
Using tar Command
# Enter the Registry directory itself (not its parent) so registry.json
# lands at the archive root, not under a my-tool/ folder.
cd /path/to/registries/my-tool
# Package the directory's contents
# COPYFILE_DISABLE=1 prevents macOS tar from adding AppleDouble (._*)
# metadata files; the server will reject any file not declared in
# registry.json.
COPYFILE_DISABLE=1 tar -czf ../my-tool-1.0.0.tar.gz .
# Verify package contents
tar -tzf ../my-tool-1.0.0.tar.gz
# Expected output (registry.json at the archive root)
# ./
# ./registry.json
# ./templates/src/config.ts
# ...Package Structure Requirements
registry.jsonmust sit at the archive root (registry.json, notmy-tool/registry.json)- Only files declared in
registry.json(files[].path,languages.*.files[].path, custommergeStrategy.script) may appear in the archive — anything else will be rejected - All
files[].pathreferences must point to regular files in the package; symlinks and other non-regular entries are rejected files[].pathvalues must be relative POSIX paths using onlyA-Z a-z 0-9 . _ @ + -per segment; percent-encoding,?,#, and backslash are not allowed- Recommended naming format:
<name>-<version>.tar.gz
Calculate SHA256 Checksum
The upload requires a checksum field in the multipart form. Use sha256sum on Linux or shasum -a 256 on macOS:
sha256sum my-tool-1.0.0.tar.gz | awk '{print $1}'
# Or on macOS:
shasum -a 256 my-tool-1.0.0.tar.gz | awk '{print $1}'Upload to Server
Use the POST /registries API to upload registry packages.
Using curl
# Set variables
SERVER_URL="https://registry.company.com"
TOKEN="your-publish-token"
PACKAGE="my-tool-1.0.0.tar.gz"
CHECKSUM=$(sha256sum "$PACKAGE" | awk '{print $1}')
# Upload with namespace token
curl -X POST "$SERVER_URL/registries" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@$PACKAGE" \
-F "checksum=$CHECKSUM"Using Admin Token
If the server has ADMIN_TOKEN configured, you can use it to publish to any namespace without per-namespace token configuration:
curl -X POST "$SERVER_URL/registries" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-F "file=@$PACKAGE" \
-F "checksum=$CHECKSUM"Admin Token vs Namespace Token
- Namespace token: Configured per-namespace in
auth.json, requirespublish: truepermission - Admin token: Set via
ADMIN_TOKENenvironment variable, bypasses namespace-level auth for uploads
Accepted Content-Type
The server validates the MIME type of the uploaded file. The following values are accepted:
application/gzipapplication/x-gzipapplication/x-tarapplication/x-compressedapplication/octet-stream
Success Response
{
"message": "Registry uploaded successfully",
"namespace": "@company",
"name": "my-tool",
"version": "1.0.0",
"path": "@company/features/my-tool/1.0.0"
}The path segments are derived from the registry's type (or explicit top-level path) — see Storage Path Resolution.
Common Errors
1. Missing Authentication Token
{
"code": "UNAUTHORIZED",
"message": "Authentication required for upload. Provide a namespace token or admin token via Authorization or X-Registry-Token."
}The server returns this before reading the multipart body (§6.20), so an unauthenticated upload doesn't burn upload bandwidth or temp disk on the server.
Solution: Add token
-H "Authorization: Bearer YOUR_TOKEN"2. Insufficient Token Permissions
{
"code": "INSUFFICIENT_PERMISSIONS",
"message": "Token does not have publish permission for namespace @company"
}Solution: Add publish: true to the token in auth.json, or use an admin token
3. Anonymous Namespace Upload Forbidden
{
"code": "ANONYMOUS_UPLOAD_FORBIDDEN",
"message": "Anonymous namespaces do not allow uploads. Use an admin token or configure namespace tokens."
}Solution: Configure tokens for the namespace in auth.json, or use the ADMIN_TOKEN
4. Checksum Mismatch
{
"code": "CHECKSUM_MISMATCH",
"message": "Checksum verification failed",
"expected": "abc123...",
"actual": "def456..."
}Solution: Recalculate the correct checksum
5. Version Already Exists
{
"code": "VERSION_EXISTS",
"message": "Registry @company/my-tool@1.0.0 already exists"
}Solution: Use a new version number
6. Namespace Not Allowed
{
"code": "FORBIDDEN_NAMESPACE",
"message": "Namespace not allowed"
}Solution: Add the namespace as a key in auth.json
CI/CD Integration
Integrate the publishing process into your CI/CD pipeline.
GitHub Actions
Create .github/workflows/publish-registry.yml:
name: Publish Registry
on:
push:
tags:
- 'v*.*.*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Pack registry
run: |
tar -czf registry-${{ steps.version.outputs.VERSION }}.tar.gz \
-C registries my-registry/
- name: Calculate checksum
id: checksum
run: |
CHECKSUM=$(sha256sum registry-${{ steps.version.outputs.VERSION }}.tar.gz | awk '{print $1}')
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_OUTPUT
- name: Upload to Registry Server
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_URL: ${{ secrets.REGISTRY_URL }}
run: |
curl -X POST "$REGISTRY_URL/registries" \
-H "Authorization: Bearer $REGISTRY_TOKEN" \
-F "file=@registry-${{ steps.version.outputs.VERSION }}.tar.gz" \
-F "checksum=${{ steps.checksum.outputs.CHECKSUM }}"Configure GitHub Secrets:
REGISTRY_TOKEN- Publishing token (namespace token or admin token)REGISTRY_URL- Registry Server URL
Other CI platforms (GitLab CI, CircleCI, etc.) follow the same pattern: in a tag-triggered job, run
tar -czf ... && sha256sum ... && curl -X POST $REGISTRY_URL/registries, injectingREGISTRY_TOKENandREGISTRY_URLvia environment variables.
Version Management
Semantic Versioning
Registry versions should follow SemVer specification:
MAJOR.MINOR.PATCH
1.0.0 → 1.0.1 (Patch update, bug fixes)
1.0.1 → 1.1.0 (Minor update, new features)
1.1.0 → 2.0.0 (Major update, breaking changes)Version List
After successful upload, Registry Server automatically updates versions.json:
{
"versions": ["1.1.0", "1.0.1", "1.0.0"]
}Versions are sorted in descending order, with the latest version first.
Manually Maintain the Version List
versions.json is maintained automatically by the upload pipeline. If you add or remove version directories directly on the server's filesystem, you must keep the corresponding <storage>/<namespace>/<name>/versions.json in sync (descending order); the server uses its contents to resolve "latest version". Prefer publishing via POST /registries to avoid manual editing.
Webhook Notifications
When a registry is successfully uploaded, webhook notifications are triggered (if configured).
Webhook Event
{
"event": "uploaded",
"timestamp": "2025-11-07T10:30:00.000Z",
"namespace": "@company",
"name": "my-tool",
"version": "1.0.0",
"path": "@company/features/my-tool/1.0.0"
}Use Cases
- CI/CD Trigger - Automatic builds and deployments
- Notifications - Send Slack/DingTalk messages
- Documentation Generation - Automatically generate and publish documentation
- Testing - Trigger automated tests
Verify Publication
Verify that the registry is available after publishing:
# View version list (use the resolved storage path — `features/` for registry:feature)
curl https://registry.company.com/registries/@company/features/my-tool/versions
# Expected output
# {"versions":["1.0.0"]}
# Get latest registry configuration
curl https://registry.company.com/registries/@company/features/my-tool
# Get specific version
curl https://registry.company.com/registries/@company/features/my-tool/1.0.0Test using CLI:
# Configure namespace
rk config set @company --url https://registry.company.com
# Add registry
rk add @company/my-tool
# View installed registries
cat rack.json
