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; export interface IRegistryVersionEntry { readonly tarballPath: string; readonly publishedAt: string; readonly publishedBy: string; } export interface IRegistryPackageEntry { readonly versions: Record; } export interface IRegistryIndex { readonly packages: Record; } 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 = {}; for (const [packageName, packageValue] of Object.entries(packagesRecord)) { const packageEntry = asRecord(packageValue); const versionsRecord = asRecord(packageEntry?.versions) ?? {}; const versions: Record = {}; 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)); }