hivecast-sdk/packages/cli/src/utils/registry-store.ts

132 lines
4.3 KiB
TypeScript

import * as fs from 'node:fs';
import * as path from 'node:path';
import { packageNameToPath } from './package-store.js';
import { maxSemver } from './versioning.js';
type JsonRecord = Record<string, unknown>;
export interface IRegistryVersionEntry {
readonly tarballPath: string;
readonly publishedAt: string;
readonly publishedBy: string;
}
export interface IRegistryPackageEntry {
readonly versions: Record<string, IRegistryVersionEntry>;
}
export interface IRegistryIndex {
readonly packages: Record<string, IRegistryPackageEntry>;
}
function asRecord(value: unknown): JsonRecord | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return null;
}
return value as JsonRecord;
}
function readRegistryIndex(indexPath: string): IRegistryIndex {
if (!fs.existsSync(indexPath)) {
return { packages: {} };
}
const parsed = JSON.parse(fs.readFileSync(indexPath, 'utf8')) as unknown;
const root = asRecord(parsed);
const packagesRecord = asRecord(root?.packages) ?? {};
const packages: Record<string, IRegistryPackageEntry> = {};
for (const [packageName, packageValue] of Object.entries(packagesRecord)) {
const packageEntry = asRecord(packageValue);
const versionsRecord = asRecord(packageEntry?.versions) ?? {};
const versions: Record<string, IRegistryVersionEntry> = {};
for (const [version, versionValue] of Object.entries(versionsRecord)) {
const versionEntry = asRecord(versionValue);
if (!versionEntry) continue;
if (typeof versionEntry.tarballPath !== 'string') continue;
if (typeof versionEntry.publishedAt !== 'string') continue;
if (typeof versionEntry.publishedBy !== 'string') continue;
versions[version] = {
tarballPath: versionEntry.tarballPath,
publishedAt: versionEntry.publishedAt,
publishedBy: versionEntry.publishedBy,
};
}
packages[packageName] = { versions };
}
return { packages };
}
function writeRegistryIndex(indexPath: string, index: IRegistryIndex): void {
fs.mkdirSync(path.dirname(indexPath), { recursive: true });
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2), 'utf8');
}
function resolveIndexPath(registryRoot: string): string {
return path.join(registryRoot, 'index.json');
}
export function publishRegistryVersion(
registryRoot: string,
packageName: string,
version: string,
sourceTarballPath: string,
publishedBy: string,
): { readonly registryTarballPath: string } {
const indexPath = resolveIndexPath(registryRoot);
const index = readRegistryIndex(indexPath);
const packageEntry = index.packages[packageName] ?? { versions: {} };
if (packageEntry.versions[version]) {
throw new Error(`MX_VERSION_EXISTS: ${packageName}@${version} already published`);
}
const packageVersionDir = path.join(registryRoot, 'packages', packageNameToPath(packageName), version);
fs.mkdirSync(packageVersionDir, { recursive: true });
const registryTarballPath = path.join(packageVersionDir, 'package.tar.gz');
fs.copyFileSync(sourceTarballPath, registryTarballPath);
packageEntry.versions[version] = {
tarballPath: registryTarballPath,
publishedAt: new Date().toISOString(),
publishedBy,
};
index.packages[packageName] = packageEntry;
writeRegistryIndex(indexPath, index);
return { registryTarballPath };
}
export function resolveRegistryTarball(
registryRoot: string,
packageName: string,
requestedVersion?: string,
): { readonly version: string; readonly tarballPath: string } {
const index = readRegistryIndex(resolveIndexPath(registryRoot));
const packageEntry = index.packages[packageName];
if (!packageEntry) {
throw new Error(`MX_PACKAGE_NOT_FOUND: ${packageName}`);
}
const version = requestedVersion ?? maxSemver(Object.keys(packageEntry.versions));
if (!version || !packageEntry.versions[version]) {
throw new Error(`MX_PACKAGE_NOT_FOUND: ${packageName}@${requestedVersion ?? 'latest'}`);
}
return {
version,
tarballPath: packageEntry.versions[version]!.tarballPath,
};
}
export function getLatestRegistryVersion(registryRoot: string, packageName: string): string | null {
const index = readRegistryIndex(resolveIndexPath(registryRoot));
const packageEntry = index.packages[packageName];
if (!packageEntry) {
return null;
}
return maxSemver(Object.keys(packageEntry.versions));
}