feat(registry): add generate-registry.sh script and docs
Shell script reads .pkg ZIPs, extracts manifests, and emits a registry.json for self-hosted package discovery. Docs cover the JSON format, admin config, and self-hosting setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
94
docs/PACKAGE-REGISTRY.md
Normal file
94
docs/PACKAGE-REGISTRY.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Package Registry
|
||||
|
||||
The package registry lets admins browse and install packages from an external
|
||||
JSON index — a lightweight alternative to manually uploading `.pkg` files.
|
||||
|
||||
## Registry JSON Format
|
||||
|
||||
The registry is a static JSON file matching the `RegistryResponse` struct:
|
||||
|
||||
```json
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"id": "notes",
|
||||
"title": "Notes",
|
||||
"version": "0.8.0",
|
||||
"description": "Markdown notes with backlinks and graph view",
|
||||
"author": "switchboard",
|
||||
"type": "extension",
|
||||
"tier": "core",
|
||||
"download_url": "https://cdn.example.com/pkg/notes.pkg",
|
||||
"size": 48200,
|
||||
"updated_at": "2026-03-20T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
||||
| Field | Description |
|
||||
|----------------|------------------------------------------------|
|
||||
| `id` | Package identifier (matches `manifest.json`) |
|
||||
| `title` | Display name |
|
||||
| `version` | Semver version string |
|
||||
| `description` | Short description |
|
||||
| `download_url` | HTTPS URL to the `.pkg` file (must be HTTPS) |
|
||||
|
||||
### Optional Fields
|
||||
|
||||
| Field | Description |
|
||||
|--------------|------------------------------------------|
|
||||
| `author` | Package author |
|
||||
| `type` | `extension` or `library` |
|
||||
| `tier` | `core`, `official`, or `community` |
|
||||
| `size` | File size in bytes |
|
||||
| `updated_at` | ISO 8601 timestamp of last update |
|
||||
|
||||
## Configuring the Registry URL
|
||||
|
||||
### Via Admin UI
|
||||
|
||||
Navigate to **Admin > Settings > Package Registry** and enter the registry URL.
|
||||
|
||||
### Via API
|
||||
|
||||
```bash
|
||||
curl -X PUT /api/v1/admin/settings/package_registry \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"value": {"url": "https://cdn.example.com/pkg/registry.json"}}'
|
||||
```
|
||||
|
||||
## Generating a Registry
|
||||
|
||||
Use `scripts/generate-registry.sh` to build a `registry.json` from a
|
||||
directory of `.pkg` files:
|
||||
|
||||
```bash
|
||||
# Default: reads dist/, uses placeholder base URL
|
||||
./scripts/generate-registry.sh
|
||||
|
||||
# Custom directory and base URL
|
||||
./scripts/generate-registry.sh ./my-packages https://cdn.example.com/pkg > registry.json
|
||||
```
|
||||
|
||||
Requirements: `jq`, `unzip`, and `stat` (GNU or BSD).
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
Any HTTPS-capable file server works. Upload your `.pkg` files and the
|
||||
generated `registry.json` to the same directory, then point the admin
|
||||
setting to the `registry.json` URL.
|
||||
|
||||
Example with a static file server:
|
||||
|
||||
```
|
||||
/var/www/packages/
|
||||
registry.json
|
||||
notes.pkg
|
||||
chat.pkg
|
||||
chat-core.pkg
|
||||
```
|
||||
|
||||
The registry is fetched and cached for 5 minutes on each browse request.
|
||||
81
scripts/generate-registry.sh
Executable file
81
scripts/generate-registry.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# generate-registry.sh — Build a registry.json from a directory of .pkg files.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/generate-registry.sh [PKG_DIR] [BASE_URL]
|
||||
#
|
||||
# Arguments:
|
||||
# PKG_DIR Directory containing .pkg files (default: dist/)
|
||||
# BASE_URL HTTPS base URL for download links (default: https://example.com/packages)
|
||||
#
|
||||
# Output:
|
||||
# Writes registry.json to stdout. Redirect to a file:
|
||||
# ./scripts/generate-registry.sh dist/ https://cdn.example.com/pkg > registry.json
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PKG_DIR="${1:-dist}"
|
||||
BASE_URL="${2:-https://example.com/packages}"
|
||||
|
||||
# Strip trailing slash
|
||||
BASE_URL="${BASE_URL%/}"
|
||||
|
||||
if [ ! -d "$PKG_DIR" ]; then
|
||||
echo "Error: directory '$PKG_DIR' not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Collect entries
|
||||
entries=()
|
||||
|
||||
for pkg in "$PKG_DIR"/*.pkg; do
|
||||
[ -f "$pkg" ] || continue
|
||||
|
||||
# Extract manifest.json from the ZIP archive
|
||||
manifest=$(unzip -p "$pkg" manifest.json 2>/dev/null) || {
|
||||
echo "Warning: skipping $pkg (no manifest.json)" >&2
|
||||
continue
|
||||
}
|
||||
|
||||
# Read fields from manifest
|
||||
id=$(echo "$manifest" | jq -r '.id // empty')
|
||||
title=$(echo "$manifest" | jq -r '.title // .id')
|
||||
version=$(echo "$manifest" | jq -r '.version // "0.0.0"')
|
||||
description=$(echo "$manifest" | jq -r '.description // ""')
|
||||
author=$(echo "$manifest" | jq -r '.author // ""')
|
||||
type=$(echo "$manifest" | jq -r '.type // "extension"')
|
||||
tier=$(echo "$manifest" | jq -r '.tier // "community"')
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
echo "Warning: skipping $pkg (no id in manifest)" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
filename=$(basename "$pkg")
|
||||
size=$(stat -c%s "$pkg" 2>/dev/null || stat -f%z "$pkg" 2>/dev/null || echo 0)
|
||||
updated_at=$(date -r "$pkg" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
entry=$(jq -n \
|
||||
--arg id "$id" \
|
||||
--arg title "$title" \
|
||||
--arg version "$version" \
|
||||
--arg description "$description" \
|
||||
--arg author "$author" \
|
||||
--arg type "$type" \
|
||||
--arg tier "$tier" \
|
||||
--arg download_url "$BASE_URL/$filename" \
|
||||
--argjson size "$size" \
|
||||
--arg updated_at "$updated_at" \
|
||||
'{id: $id, title: $title, version: $version, description: $description,
|
||||
author: $author, type: $type, tier: $tier, download_url: $download_url,
|
||||
size: $size, updated_at: $updated_at}')
|
||||
|
||||
entries+=("$entry")
|
||||
done
|
||||
|
||||
# Build final JSON
|
||||
if [ ${#entries[@]} -eq 0 ]; then
|
||||
echo '{"packages": []}' | jq .
|
||||
else
|
||||
printf '%s\n' "${entries[@]}" | jq -s '{packages: .}'
|
||||
fi
|
||||
Reference in New Issue
Block a user